From f09d53952127c1b36a94033f7b573bbeb3b38550 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Sat, 22 Sep 2018 16:50:39 +1000 Subject: [PATCH] GUI unit tests in both share and receive mode --- .travis.yml | 3 +- unit_tests/__init__.py | 0 unit_tests/conftest.py | 160 ++++++++++ .../onionshare_receive_mode_upload_test.py | 258 ++++++++++++++++ ...re_receive_mode_upload_test_public_mode.py | 258 ++++++++++++++++ .../onionshare_share_mode_download_test.py | 287 ++++++++++++++++++ ...re_share_mode_download_test_public_mode.py | 287 ++++++++++++++++++ ...hare_share_mode_download_test_stay_open.py | 256 ++++++++++++++++ unit_tests/onionshare_timer_test.py | 142 +++++++++ unit_tests/run_unit_tests.sh | 5 + 10 files changed, 1655 insertions(+), 1 deletion(-) create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/conftest.py create mode 100644 unit_tests/onionshare_receive_mode_upload_test.py create mode 100644 unit_tests/onionshare_receive_mode_upload_test_public_mode.py create mode 100644 unit_tests/onionshare_share_mode_download_test.py create mode 100644 unit_tests/onionshare_share_mode_download_test_public_mode.py create mode 100644 unit_tests/onionshare_share_mode_download_test_stay_open.py create mode 100644 unit_tests/onionshare_timer_test.py create mode 100755 unit_tests/run_unit_tests.sh diff --git a/.travis.yml b/.travis.yml index afbaa887..71778af4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ python: # command to install dependencies install: - pip install -r install/requirements.txt - - pip install pytest-cov coveralls flake8 + - pip install pytest-cov coveralls flake8 pytest-faulthandler pytest-ordering before_script: # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics @@ -17,5 +17,6 @@ before_script: - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics # command to run tests script: pytest --cov=onionshare test/ +script: cd unit_tests && bash run_unit_tests.sh after_success: - coveralls diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/unit_tests/conftest.py b/unit_tests/conftest.py new file mode 100644 index 00000000..8ac7efb8 --- /dev/null +++ b/unit_tests/conftest.py @@ -0,0 +1,160 @@ +import sys +# Force tests to look for resources in the source code tree +sys.onionshare_dev_mode = True + +import os +import shutil +import tempfile + +import pytest + +from onionshare import common, web, settings + +@pytest.fixture +def temp_dir_1024(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). + """ + + tmp_dir = tempfile.mkdtemp() + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + return tmp_dir + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_dir_1024_delete(): + """ Create a temporary directory that has a single file of a + particular size (1024 bytes). The temporary directory (including + the file inside) will be deleted after fixture usage. + """ + + with tempfile.TemporaryDirectory() as tmp_dir: + tmp_file, tmp_file_path = tempfile.mkstemp(dir=tmp_dir) + with open(tmp_file, 'wb') as f: + f.write(b'*' * 1024) + yield tmp_dir + + +@pytest.fixture +def temp_file_1024(): + """ Create a temporary file of a particular size (1024 bytes). """ + + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + tmp_file.write(b'*' * 1024) + return tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture +def temp_file_1024_delete(): + """ + Create a temporary file of a particular size (1024 bytes). + The temporary file will be deleted after fixture usage. + """ + + with tempfile.NamedTemporaryFile() as tmp_file: + tmp_file.write(b'*' * 1024) + tmp_file.flush() + yield tmp_file.name + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def custom_zw(): + zw = web.share_mode.ZipWriter( + common.Common(), + zip_filename=common.Common.random_string(4, 6), + processed_size_callback=lambda _: 'custom_callback' + ) + yield zw + zw.close() + os.remove(zw.zip_filename) + + +# pytest > 2.9 only needs @pytest.fixture +@pytest.yield_fixture(scope='session') +def default_zw(): + zw = web.share_mode.ZipWriter(common.Common()) + yield zw + zw.close() + tmp_dir = os.path.dirname(zw.zip_filename) + shutil.rmtree(tmp_dir) + + +@pytest.fixture +def locale_en(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('en_US', 'UTF-8')) + + +@pytest.fixture +def locale_fr(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('fr_FR', 'UTF-8')) + + +@pytest.fixture +def locale_invalid(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('xx_XX', 'UTF-8')) + + +@pytest.fixture +def locale_ru(monkeypatch): + monkeypatch.setattr('locale.getdefaultlocale', lambda: ('ru_RU', 'UTF-8')) + + +@pytest.fixture +def platform_darwin(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Darwin') + + +@pytest.fixture # (scope="session") +def platform_linux(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Linux') + + +@pytest.fixture +def platform_windows(monkeypatch): + monkeypatch.setattr('platform.system', lambda: 'Windows') + + +@pytest.fixture +def sys_argv_sys_prefix(monkeypatch): + monkeypatch.setattr('sys.argv', [sys.prefix]) + + +@pytest.fixture +def sys_frozen(monkeypatch): + monkeypatch.setattr('sys.frozen', True, raising=False) + + +@pytest.fixture +def sys_meipass(monkeypatch): + monkeypatch.setattr( + 'sys._MEIPASS', os.path.expanduser('~'), raising=False) + + +@pytest.fixture # (scope="session") +def sys_onionshare_dev_mode(monkeypatch): + monkeypatch.setattr('sys.onionshare_dev_mode', True, raising=False) + + +@pytest.fixture +def time_time_100(monkeypatch): + monkeypatch.setattr('time.time', lambda: 100) + + +@pytest.fixture +def time_strftime(monkeypatch): + monkeypatch.setattr('time.strftime', lambda _: 'Jun 06 2013 11:05:00') + +@pytest.fixture +def common_obj(): + return common.Common() + +@pytest.fixture +def settings_obj(sys_onionshare_dev_mode, platform_linux): + _common = common.Common() + _common.version = 'DUMMY_VERSION_1.2.3' + return settings.Settings(_common) diff --git a/unit_tests/onionshare_receive_mode_upload_test.py b/unit_tests/onionshare_receive_mode_upload_test.py new file mode 100644 index 00000000..b99faac0 --- /dev/null +++ b/unit_tests/onionshare_receive_mode_upload_test.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json +import requests + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + def setUpClass(cls): + '''Create the GUI''' + # Create our test file + testfile = open('/tmp/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() + common.define_css() + + # Start the Onion + strings.load_strings(common) + + testonion = onion.Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + + test_settings = { + "auth_password": "", + "auth_type": "no_auth", + "autoupdate_timestamp": "", + "close_after_first_download": True, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "downloads_dir": "/tmp/OnionShare", + "hidservauth_string": "", + "no_bridges": True, + "private_key": "", + "public_mode": False, + "receive_allow_receiver_shutdown": True, + "save_private_key": False, + "shutdown_timeout": False, + "slug": "", + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "systray_notifications": True, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_meek_lite_amazon": False, + "tor_bridges_use_custom_bridges": "", + "tor_bridges_use_obfs4": False, + "use_stealth": False, + "use_legacy_v2_onions": False, + "use_autoupdate": True, + "version": "1.3.1" + } + testsettings = '/tmp/testsettings.json' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + '''Test that the info widget along top of screen is not shown because we have a file''' + self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + + @pytest.mark.run(order=6) + def test_click_receive_mode(self): + '''Test that we can switch to Receive Mode by clicking the button''' + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + + @pytest.mark.run(order=7) + def test_uploads_section_is_visible(self): + '''Test that the Uploads section is visible and that the No Uploads Yet label is present''' + self.assertTrue(self.gui.receive_mode.uploads.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.receive_mode.server_status.status, 1) + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=13) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=14) + def test_have_a_slug(self): + '''Test that we have a valid slug''' + self.assertRegex(self.gui.receive_mode.server_status.web.slug, r'(\w+)-(\w+)') + + @pytest.mark.run(order=15) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=16) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=17) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Receiving''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + + @pytest.mark.run(order=18) + def test_web_page(self): + '''Test that the web page contains the term Select the files you want to send, then click''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {} HTTP/1.0\r\n'.format(self.gui.receive_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Select the files you want to send, then click "Send Files"' in f.read()) + f.close() + + @pytest.mark.run(order=19) + def test_upload_file(self): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test.txt')) + + @pytest.mark.run(order=20) + def test_uploads_widget_present(self): + '''Test that the No Uploads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + + @pytest.mark.run(order=21) + def test_upload_count_incremented(self): + '''Test that the Upload Count has incremented''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 1) + + @pytest.mark.run(order=22) + def test_upload_same_file_is_renamed(self): + '''Test that we can upload the same file and that it gets renamed''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/{}/upload'.format(self.gui.app.port, self.gui.receive_mode.web.slug), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test-2.txt')) + + @pytest.mark.run(order=23) + def test_upload_count_incremented_again(self): + '''Test that the Upload Count has incremented again''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 2) + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + '''Test that the server stops when we click Stop''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.receive_mode.server_status.status, 0) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_receive_mode_upload_test_public_mode.py b/unit_tests/onionshare_receive_mode_upload_test_public_mode.py new file mode 100644 index 00000000..d309e5b1 --- /dev/null +++ b/unit_tests/onionshare_receive_mode_upload_test_public_mode.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json +import requests + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + def setUpClass(cls): + '''Create the GUI''' + # Create our test file + testfile = open('/tmp/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() + common.define_css() + + # Start the Onion + strings.load_strings(common) + + testonion = onion.Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + + test_settings = { + "auth_password": "", + "auth_type": "no_auth", + "autoupdate_timestamp": "", + "close_after_first_download": True, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "downloads_dir": "/tmp/OnionShare", + "hidservauth_string": "", + "no_bridges": True, + "private_key": "", + "public_mode": True, + "receive_allow_receiver_shutdown": True, + "save_private_key": False, + "shutdown_timeout": False, + "slug": "", + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "systray_notifications": True, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_meek_lite_amazon": False, + "tor_bridges_use_custom_bridges": "", + "tor_bridges_use_obfs4": False, + "use_stealth": False, + "use_legacy_v2_onions": False, + "use_autoupdate": True, + "version": "1.3.1" + } + testsettings = '/tmp/testsettings.json' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + os.remove('/tmp/OnionShare/test.txt') + os.remove('/tmp/OnionShare/test-2.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_info_widget_is_not_visible(self): + '''Test that the info widget along top of screen is not shown because we have a file''' + self.assertFalse(self.gui.receive_mode.info_widget.isVisible()) + + @pytest.mark.run(order=6) + def test_click_receive_mode(self): + '''Test that we can switch to Receive Mode by clicking the button''' + QtTest.QTest.mouseClick(self.gui.receive_mode_button, QtCore.Qt.LeftButton) + self.assertTrue(self.gui.mode, self.gui.MODE_RECEIVE) + + @pytest.mark.run(order=7) + def test_uploads_section_is_visible(self): + '''Test that the Uploads section is visible and that the No Uploads Yet label is present''' + self.assertTrue(self.gui.receive_mode.uploads.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.receive_mode.server_status.status, 1) + + @pytest.mark.run(order=9) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=10) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=11) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.receive_mode.server_status.status, 2) + + @pytest.mark.run(order=12) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=13) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=14) + def test_have_no_slug(self): + '''Test that we have a valid slug''' + self.assertIsNone(self.gui.share_mode.server_status.web.slug) + + @pytest.mark.run(order=15) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.receive_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=16) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.receive_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=17) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Receiving''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_started', True)) + + @pytest.mark.run(order=18) + def test_web_page(self): + '''Test that the web page contains the term Select the files you want to send, then click''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET / HTTP/1.0\r\n' + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Select the files you want to send, then click "Send Files"' in f.read()) + f.close() + + @pytest.mark.run(order=19) + def test_upload_file(self): + '''Test that we can upload the file''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test.txt')) + + @pytest.mark.run(order=20) + def test_uploads_widget_present(self): + '''Test that the No Uploads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.receive_mode.uploads.no_uploads_label.isVisible()) + self.assertTrue(self.gui.receive_mode.uploads.clear_history_button.isVisible()) + + @pytest.mark.run(order=21) + def test_upload_count_incremented(self): + '''Test that the Upload Count has incremented''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 1) + + @pytest.mark.run(order=22) + def test_upload_same_file_is_renamed(self): + '''Test that we can upload the same file and that it gets renamed''' + files = {'file[]': open('/tmp/test.txt', 'rb')} + response = requests.post('http://127.0.0.1:{}/upload'.format(self.gui.app.port), files=files) + QtTest.QTest.qWait(2000) + self.assertTrue(os.path.isfile('/tmp/OnionShare/test-2.txt')) + + @pytest.mark.run(order=23) + def test_upload_count_incremented_again(self): + '''Test that the Upload Count has incremented again''' + self.assertEquals(self.gui.receive_mode.uploads_completed, 2) + + @pytest.mark.run(order=24) + def test_server_is_stopped(self): + '''Test that the server stops when we click Stop''' + QtTest.QTest.mouseClick(self.gui.receive_mode.server_status.server_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.receive_mode.server_status.status, 0) + + @pytest.mark.run(order=25) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + QtTest.QTest.qWait(2000) + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=26) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed''' + self.assertEquals(self.gui.receive_mode.server_status_label.text(), strings._('gui_status_indicator_receive_stopped', True)) + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test.py b/unit_tests/onionshare_share_mode_download_test.py new file mode 100644 index 00000000..aa8dcdaa --- /dev/null +++ b/unit_tests/onionshare_share_mode_download_test.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + def setUpClass(cls): + '''Create the GUI''' + # Create our test file + testfile = open('/tmp/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() + common.define_css() + + # Start the Onion + strings.load_strings(common) + + testonion = onion.Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + + test_settings = { + "auth_password": "", + "auth_type": "no_auth", + "autoupdate_timestamp": "", + "close_after_first_download": True, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "downloads_dir": "/tmp/OnionShare", + "hidservauth_string": "", + "no_bridges": True, + "private_key": "", + "public_mode": False, + "receive_allow_receiver_shutdown": True, + "save_private_key": False, + "shutdown_timeout": False, + "slug": "", + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "systray_notifications": True, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_meek_lite_amazon": False, + "tor_bridges_use_custom_bridges": "", + "tor_bridges_use_obfs4": False, + "use_stealth": False, + "use_legacy_v2_onions": False, + "use_autoupdate": True, + "version": "1.3.1" + } + testsettings = '/tmp/testsettings.json' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + '''Test that the info widget along top of screen is shown because we have a file''' + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + @pytest.mark.run(order=7) + def test_downloads_section_is_visible(self): + '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=13) + def test_add_delete_buttons_now_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=17) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=18) + def test_have_a_slug(self): + '''Test that we have a valid slug''' + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Sharing''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + @pytest.mark.run(order=22) + def test_web_page(self): + '''Test that the web page contains the term Total size''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {} HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Total size' in f.read()) + f.close() + + @pytest.mark.run(order=23) + def test_download_share(self): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {}/download HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + @pytest.mark.run(order=24) + def test_downloads_widget_present(self): + QtTest.QTest.qWait(1000) + '''Test that the No Downloads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + '''Test that the server stopped automatically when we downloaded the share''' + self.assertEquals(self.gui.share_mode.server_status.status, 0) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed because download occurred''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + + @pytest.mark.run(order=28) + def test_add_button_visible_again(self): + '''Test that the add button should be visible again''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test_public_mode.py b/unit_tests/onionshare_share_mode_download_test_public_mode.py new file mode 100644 index 00000000..59905149 --- /dev/null +++ b/unit_tests/onionshare_share_mode_download_test_public_mode.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + def setUpClass(cls): + '''Create the GUI''' + # Create our test file + testfile = open('/tmp/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() + common.define_css() + + # Start the Onion + strings.load_strings(common) + + testonion = onion.Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + + test_settings = { + "auth_password": "", + "auth_type": "no_auth", + "autoupdate_timestamp": "", + "close_after_first_download": True, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "downloads_dir": "/tmp/OnionShare", + "hidservauth_string": "", + "no_bridges": True, + "private_key": "", + "public_mode": True, + "receive_allow_receiver_shutdown": True, + "save_private_key": False, + "shutdown_timeout": False, + "slug": "", + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "systray_notifications": True, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_meek_lite_amazon": False, + "tor_bridges_use_custom_bridges": "", + "tor_bridges_use_obfs4": False, + "use_stealth": False, + "use_legacy_v2_onions": False, + "use_autoupdate": True, + "version": "1.3.1" + } + testsettings = '/tmp/testsettings.json' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + '''Test that the info widget along top of screen is shown because we have a file''' + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + @pytest.mark.run(order=7) + def test_downloads_section_is_visible(self): + '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=13) + def test_add_delete_buttons_now_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=17) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=18) + def test_have_no_slug(self): + '''Test that we have a valid slug''' + self.assertIsNone(self.gui.share_mode.server_status.web.slug) + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Sharing''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + @pytest.mark.run(order=22) + def test_web_page(self): + '''Test that the web page contains the term Total size''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET / HTTP/1.0\r\n' + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/webpage', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + f = open('/tmp/webpage') + self.assertTrue('Total size' in f.read()) + f.close() + + @pytest.mark.run(order=23) + def test_download_share(self): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET /download HTTP/1.0\r\n' + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + @pytest.mark.run(order=24) + def test_downloads_widget_present(self): + QtTest.QTest.qWait(1000) + '''Test that the No Downloads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + @pytest.mark.run(order=25) + def test_server_is_stopped(self): + '''Test that the server stopped automatically when we downloaded the share''' + self.assertEquals(self.gui.share_mode.server_status.status, 0) + + @pytest.mark.run(order=26) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=27) + def test_server_status_indicator_says_closed(self): + '''Test that the Server Status indicator shows we closed because download occurred''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('closing_automatically', True)) + + @pytest.mark.run(order=28) + def test_add_button_visible_again(self): + '''Test that the add button should be visible again''' + self.assertTrue(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_share_mode_download_test_stay_open.py b/unit_tests/onionshare_share_mode_download_test_stay_open.py new file mode 100644 index 00000000..82a0ac87 --- /dev/null +++ b/unit_tests/onionshare_share_mode_download_test_stay_open.py @@ -0,0 +1,256 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + def setUpClass(cls): + '''Create the GUI''' + # Create our test file + testfile = open('/tmp/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() + common.define_css() + + # Start the Onion + strings.load_strings(common) + + testonion = onion.Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + + test_settings = { + "auth_password": "", + "auth_type": "no_auth", + "autoupdate_timestamp": "", + "close_after_first_download": False, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "downloads_dir": "/tmp/OnionShare", + "hidservauth_string": "", + "no_bridges": True, + "private_key": "", + "public_mode": False, + "receive_allow_receiver_shutdown": True, + "save_private_key": False, + "shutdown_timeout": False, + "slug": "", + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "systray_notifications": True, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_meek_lite_amazon": False, + "tor_bridges_use_custom_bridges": "", + "tor_bridges_use_obfs4": False, + "use_stealth": False, + "use_legacy_v2_onions": False, + "use_autoupdate": True, + "version": "1.3.1" + } + testsettings = '/tmp/testsettings.json' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded_and_tor_bootstrapped(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_windowTitle_seen(self): + '''Test that the window title is OnionShare''' + self.assertEqual(self.gui.windowTitle(), 'OnionShare') + + @pytest.mark.run(order=3) + def test_settings_button_is_visible(self): + '''Test that the settings button is visible''' + self.assertTrue(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=4) + def test_server_status_bar_is_visible(self): + '''Test that the status bar is visible''' + self.assertTrue(self.gui.status_bar.isVisible()) + + @pytest.mark.run(order=5) + def test_file_selection_widget_has_a_file(self): + '''Test that the number of files in the list is 1''' + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 1) + + @pytest.mark.run(order=6) + def test_info_widget_is_visible(self): + '''Test that the info widget along top of screen is shown because we have a file''' + self.assertTrue(self.gui.share_mode.info_widget.isVisible()) + + @pytest.mark.run(order=7) + def test_downloads_section_is_visible(self): + '''Test that the Downloads section is visible and that the No Downloads Yet label is present''' + self.assertTrue(self.gui.share_mode.downloads.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + + @pytest.mark.run(order=8) + def test_deleting_only_file_hides_delete_button(self): + '''Test that clicking on the file item shows the delete button. Test that deleting the only item in the list hides the delete button''' + rect = self.gui.share_mode.server_status.file_selection.file_list.visualItemRect(self.gui.share_mode.server_status.file_selection.file_list.item(0)) + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.viewport(), QtCore.Qt.LeftButton, pos=rect.center()) + # Delete button should be visible + self.assertTrue(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + # Click delete, and since there's no more files, the delete button should be hidden + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.delete_button, QtCore.Qt.LeftButton) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=9) + def test_add_a_file_and_delete_using_its_delete_widget(self): + '''Test that we can also delete a file by clicking on its [X] widget''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.file_selection.file_list.item(0).item_button, QtCore.Qt.LeftButton) + self.assertEquals(self.gui.share_mode.server_status.file_selection.get_num_files(), 0) + + @pytest.mark.run(order=10) + def test_file_selection_widget_readd_files(self): + '''Re-add some files to the list so we can share''' + self.gui.share_mode.server_status.file_selection.file_list.add_file('/etc/hosts') + self.gui.share_mode.server_status.file_selection.file_list.add_file('/tmp/test.txt') + self.assertEqual(self.gui.share_mode.server_status.file_selection.get_num_files(), 2) + + @pytest.mark.run(order=11) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=12) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=13) + def test_add_delete_buttons_now_hidden(self): + '''Test that the add and delete buttons are hidden when the server starts''' + self.assertFalse(self.gui.share_mode.server_status.file_selection.add_button.isVisible()) + self.assertFalse(self.gui.share_mode.server_status.file_selection.delete_button.isVisible()) + + @pytest.mark.run(order=14) + def test_settings_button_is_hidden(self): + '''Test that the settings button is hidden when the server starts''' + self.assertFalse(self.gui.settings_button.isVisible()) + + @pytest.mark.run(order=15) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=16) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + # Running in local mode, so we have no .onion + #@pytest.mark.run(order=17) + #def test_have_an_onion_service(self): + # '''Test that we have a valid Onion URL''' + # self.assertRegex(self.gui.app.onion_host, r'[a-z2-7].onion') + # self.assertEqual(len(self.gui.app.onion_host), 62) + + @pytest.mark.run(order=18) + def test_have_a_slug(self): + '''Test that we have a valid slug''' + self.assertRegex(self.gui.share_mode.server_status.web.slug, r'(\w+)-(\w+)') + + @pytest.mark.run(order=19) + def test_url_description_shown(self): + '''Test that the URL label is showing''' + self.assertTrue(self.gui.share_mode.server_status.url_description.isVisible()) + + @pytest.mark.run(order=20) + def test_have_copy_url_button(self): + '''Test that the Copy URL button is shown''' + self.assertTrue(self.gui.share_mode.server_status.copy_url_button.isVisible()) + + @pytest.mark.run(order=21) + def test_server_status_indicator_says_sharing(self): + '''Test that the Server Status indicator shows we are Sharing''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_started', True)) + + @pytest.mark.run(order=22) + def test_download_share(self): + '''Test that we can download the share''' + s = socks.socksocket() + s.settimeout(60) + s.connect(('127.0.0.1', self.gui.app.port)) + + http_request = 'GET {}/download HTTP/1.0\r\n'.format(self.gui.share_mode.server_status.web.slug) + http_request += 'Host: 127.0.0.1\r\n' + http_request += '\r\n' + s.sendall(http_request.encode('utf-8')) + + with open('/tmp/download.zip', 'wb') as file_to_write: + while True: + data = s.recv(1024) + if not data: + break + file_to_write.write(data) + file_to_write.close() + + zip = zipfile.ZipFile('/tmp/download.zip') + self.assertEquals('onionshare', zip.read('test.txt').decode('utf-8')) + + @pytest.mark.run(order=23) + def test_downloads_widget_present(self): + QtTest.QTest.qWait(1000) + '''Test that the No Downloads Yet label is hidden, that Clear History is present''' + self.assertFalse(self.gui.share_mode.downloads.no_downloads_label.isVisible()) + self.assertTrue(self.gui.share_mode.downloads.clear_history_button.isVisible()) + + @pytest.mark.run(order=24) + def test_server_is_not_stopped(self): + '''Test that the server stayed open after we downloaded the share''' + self.assertEquals(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=25) + def test_web_service_is_running(self): + '''Test that the web server is still running''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.assertEquals(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=26) + def test_download_count_incremented(self): + '''Test that the Download Count has incremented''' + self.assertEquals(self.gui.share_mode.downloads_completed, 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/onionshare_timer_test.py b/unit_tests/onionshare_timer_test.py new file mode 100644 index 00000000..ed20c1c0 --- /dev/null +++ b/unit_tests/onionshare_timer_test.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import os +import sys +import unittest +import socket +import pytest +import zipfile +import socks +import json + +from PyQt5 import QtCore, QtWidgets, QtTest + +from onionshare.common import Common +from onionshare.web import Web +from onionshare import onion, strings +from onionshare_gui import * + +app = QtWidgets.QApplication(sys.argv) + +class OnionShareGuiTest(unittest.TestCase): + '''Test the OnionShare GUI''' + @classmethod + def setUpClass(cls): + '''Create the GUI''' + # Create our test file + testfile = open('/tmp/test.txt', 'w') + testfile.write('onionshare') + testfile.close() + common = Common() + common.define_css() + + # Start the Onion + strings.load_strings(common) + + testonion = onion.Onion(common) + global qtapp + qtapp = Application(common) + app = OnionShare(common, testonion, True, 0) + + web = Web(common, False, True) + + test_settings = { + "auth_password": "", + "auth_type": "no_auth", + "autoupdate_timestamp": "", + "close_after_first_download": True, + "connection_type": "bundled", + "control_port_address": "127.0.0.1", + "control_port_port": 9051, + "downloads_dir": "/tmp/OnionShare", + "hidservauth_string": "", + "no_bridges": True, + "private_key": "", + "public_mode": False, + "receive_allow_receiver_shutdown": True, + "save_private_key": False, + "shutdown_timeout": True, + "slug": "", + "socks_address": "127.0.0.1", + "socks_port": 9050, + "socket_file_path": "/var/run/tor/control", + "systray_notifications": True, + "tor_bridges_use_meek_lite_azure": False, + "tor_bridges_use_meek_lite_amazon": False, + "tor_bridges_use_custom_bridges": "", + "tor_bridges_use_obfs4": False, + "use_stealth": False, + "use_legacy_v2_onions": False, + "use_autoupdate": True, + "version": "1.3.1" + } + testsettings = '/tmp/testsettings.json' + open(testsettings, 'w').write(json.dumps(test_settings)) + + cls.gui = OnionShareGui(common, testonion, qtapp, app, ['/tmp/test.txt'], testsettings, True) + + @classmethod + def tearDownClass(cls): + '''Clean up after tests''' + os.remove('/tmp/test.txt') + + @pytest.mark.run(order=1) + def test_gui_loaded(self): + '''Test that the GUI actually is shown''' + self.assertTrue(self.gui.show) + + @pytest.mark.run(order=2) + def test_set_timeout(self): + '''Test that the timeout can be set''' + timer = QtCore.QDateTime.currentDateTime().addSecs(120) + self.gui.share_mode.server_status.shutdown_timeout.setDateTime(timer) + self.assertTrue(self.gui.share_mode.server_status.shutdown_timeout.dateTime(), timer) + + @pytest.mark.run(order=3) + def test_server_working_on_start_button_pressed(self): + '''Test we can start the service''' + QtTest.QTest.mouseClick(self.gui.share_mode.server_status.server_button, QtCore.Qt.LeftButton) + + # Should be in SERVER_WORKING state + self.assertEqual(self.gui.share_mode.server_status.status, 1) + + @pytest.mark.run(order=4) + def test_server_status_indicator_says_starting(self): + '''Test that the Server Status indicator shows we are Starting''' + self.assertEquals(self.gui.share_mode.server_status_label.text(), strings._('gui_status_indicator_share_working', True)) + + @pytest.mark.run(order=5) + def test_a_server_is_started(self): + '''Test that the server has started''' + QtTest.QTest.qWait(2000) + # Should now be in SERVER_STARTED state + self.assertEqual(self.gui.share_mode.server_status.status, 2) + + @pytest.mark.run(order=6) + def test_a_web_server_is_running(self): + '''Test that the web server has started''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + self.assertEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + + @pytest.mark.run(order=7) + def test_timeout_widget_hidden(self): + '''Test that the timeout widget is hidden when share has started''' + self.assertFalse(self.gui.share_mode.server_status.shutdown_timeout_container.isVisible()) + + @pytest.mark.run(order=8) + def test_server_timed_out(self): + '''Test that the server has timed out after the timer ran out''' + QtTest.QTest.qWait(100000) + # We should have timed out now + self.assertEqual(self.gui.share_mode.server_status.status, 0) + + @pytest.mark.run(order=9) + def test_web_service_is_stopped(self): + '''Test that the web server also stopped''' + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # We should be closed by now. Fail if not! + self.assertNotEqual(sock.connect_ex(('127.0.0.1',self.gui.app.port)), 0) + +if __name__ == "__main__": + unittest.main() diff --git a/unit_tests/run_unit_tests.sh b/unit_tests/run_unit_tests.sh new file mode 100755 index 00000000..d15f8a6e --- /dev/null +++ b/unit_tests/run_unit_tests.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for test in `ls -1 | egrep ^onionshare_`; do + py.test-3 $test -vvv || exit 1 +done