From 76256dc025f0ed35bc8893d4002be1c0e6173dd7 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 30 Oct 2024 19:09:25 +0100 Subject: [PATCH] added tests and final code cleaning --- .gitea/workflows/run.yaml | 28 +++++ requirements.txt | 10 ++ src/__init__.py | 0 src/controller/mainFrameController.py | 1 - src/models/data.py | 4 +- src/models/provider.py | 4 +- tests/Ai/test_ml_inference.py | 44 +++++++ tests/__init__.py | 0 tests/controller/test_mainFrameController.py | 116 +++++++++++++++++++ tests/models/test_data.py | 92 +++++++++++++++ tests/models/test_provider.py | 35 ++++++ tests/test_main.py | 33 ++++++ tests/utils/test_database.py | 62 ++++++++++ tests/utils/test_webText_Extractor.py | 72 ++++++++++++ tests/views/test_mainScreen.py | 91 +++++++++++++++ 15 files changed, 585 insertions(+), 7 deletions(-) create mode 100644 .gitea/workflows/run.yaml create mode 100644 requirements.txt create mode 100644 src/__init__.py create mode 100644 tests/Ai/test_ml_inference.py create mode 100644 tests/__init__.py create mode 100644 tests/controller/test_mainFrameController.py create mode 100644 tests/models/test_data.py create mode 100644 tests/models/test_provider.py create mode 100644 tests/test_main.py create mode 100644 tests/utils/test_database.py create mode 100644 tests/utils/test_webText_Extractor.py create mode 100644 tests/views/test_mainScreen.py diff --git a/.gitea/workflows/run.yaml b/.gitea/workflows/run.yaml new file mode 100644 index 0000000..43ee695 --- /dev/null +++ b/.gitea/workflows/run.yaml @@ -0,0 +1,28 @@ +name: Gitea Actions Demo + +on: [push] + +jobs: + Explore-Gitea-Actions: + runs-on: ubuntu-latest + steps: + - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event." + - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!" + - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}." + - name: Check out repository code + uses: actions/checkout@v4 + - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner." + - run: echo "🖥️ The workflow is now ready to test your code on the runner." + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11.7' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run tests + run: | + pytest tests/ + - run: echo "🍏 This job's status is ${{ job.status }}." +s \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4722f7e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +customtkinter +Pillow +requests +beautifulsoup4 +duckdb +langchain-community==0.3.0 +torch +transformers +pytest +pytest-mock \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/controller/mainFrameController.py b/src/controller/mainFrameController.py index db38295..bf51879 100644 --- a/src/controller/mainFrameController.py +++ b/src/controller/mainFrameController.py @@ -99,7 +99,6 @@ class MainFrameController: data = self.db.fetch_data() if data: for row in data: - print(f"ID: {row[0]}, URL: {row[1]}, Anbieter: {row[2]}, Fake News: {'Ja' if row[3] else 'Nein'}") text_data = TextData(url=row[1], provider=row[2], is_fake_news= row[3]) self.text_data_list.append(text_data) diff --git a/src/models/data.py b/src/models/data.py index 08d3a51..55a8514 100644 --- a/src/models/data.py +++ b/src/models/data.py @@ -1,5 +1,5 @@ from urllib.parse import urlparse -from typing import Optional + from utils.webTextExtractor import WebTextExtractor @@ -20,7 +20,6 @@ class TextData: def text_from_url(self)-> bool: if not self.url: - print("No url") return False if not self.text: @@ -35,7 +34,6 @@ class TextData: if self.confidence != None: output = f"Prediction: {self.result}" + f" Confidence: {self.confidence:.4f}" - print(output) return output def get_provider(self)-> str: diff --git a/src/models/provider.py b/src/models/provider.py index 3a82610..a1a2a2c 100644 --- a/src/models/provider.py +++ b/src/models/provider.py @@ -10,8 +10,6 @@ class Provider(): count_all = 0 count_fake = 0 for text_data in self.text_data_list: - #print(text_data.provider) - #print("FAKE" if text_data.is_fake_news else "REAL") count_all += 1 if text_data.is_fake_news: count_fake += 1 @@ -19,6 +17,6 @@ class Provider(): if count_all == 0: return 0.0 - return (count_fake / count_all) * 100 + return round((count_fake / count_all) * 100, 2) \ No newline at end of file diff --git a/tests/Ai/test_ml_inference.py b/tests/Ai/test_ml_inference.py new file mode 100644 index 0000000..b2778f5 --- /dev/null +++ b/tests/Ai/test_ml_inference.py @@ -0,0 +1,44 @@ +import pytest +import torch +import os +import sys + + +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, src_dir) + +from Ai.interence import VeraMindInference + +@pytest.fixture +def model_fixture(): + model_path = "VeraMind-mini" + max_len = 512 + return VeraMindInference(model_path, max_len) + +def test_init(model_fixture): + assert model_fixture.device.type == "cuda" if torch.cuda.is_available() else "cpu" + +def test_predict_fake(model_fixture): + fake_text = "Das ist sehr traurig" + prediction = model_fixture.predict(fake_text) + assert prediction["result"] == "FAKE", f"Expected FAKE, got {prediction['result']}" + assert prediction["confidence"] > 0.5, f"Confidence {prediction['confidence']} is not > 0.5" + assert prediction["is_fake"] in [True, 1], f"Expected is_fake to be True, got {prediction['is_fake']}" + +def test_predict_real(model_fixture): + real_text = "Das sind die Freitag Abend Nachrichten" + prediction = model_fixture.predict(real_text) + assert prediction["result"] == "REAL", f"Expected REAL, got {prediction['result']}" + assert prediction["confidence"] > 0.5, f"Confidence {prediction['confidence']} is not > 0.5" + assert prediction["is_fake"] in [False, 0], f"Expected is_fake to be False or 0, got {prediction['is_fake']}" + +def test_predict_confidence_range(model_fixture): + for _ in range(5): + text = "Insert a random text for testing" + prediction = model_fixture.predict(text) + assert 0 <= prediction["confidence"] <= 1, f"Confidence {prediction['confidence']} is not between 0 and 1" + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/controller/test_mainFrameController.py b/tests/controller/test_mainFrameController.py new file mode 100644 index 0000000..1e8c5c2 --- /dev/null +++ b/tests/controller/test_mainFrameController.py @@ -0,0 +1,116 @@ +import sys +import os +import pytest +from unittest.mock import MagicMock, patch + +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, src_dir) + +from controller.mainFrameController import MainFrameController +from views.mainScreen import MainFrame +from models.data import TextData + + +@pytest.fixture +def mock_main_frame(): + mock_frame = MagicMock(spec=MainFrame) + mock_frame.provider_container = MagicMock() + mock_frame.entry_url = MagicMock() + mock_frame.input_textbox = MagicMock() + return mock_frame + +@pytest.fixture +def controller(mock_main_frame): + with patch('controller.mainFrameController.VeraMindInference'), \ + patch('controller.mainFrameController.FakeNewsChecker'), \ + patch('controller.mainFrameController.ArticleRater'): + return MainFrameController(mock_main_frame) + +def test_init(controller): + assert isinstance(controller.frame, MagicMock) + assert isinstance(controller.frame.provider_container, MagicMock) + assert isinstance(controller.model_inference, MagicMock) + assert isinstance(controller.db, MagicMock) + assert isinstance(controller.rater, MagicMock) + + +def test_get_text_data(controller): + controller.frame.entry_url.get.return_value = "https://example.com" + controller.frame.input_textbox.get.return_value = "Sample text" + + with patch('models.data.TextData.text_from_url', return_value=False): + text_data = controller.get_text_data() + + assert isinstance(text_data, TextData) + assert text_data.url == "https://example.com" + assert text_data.text == "Sample text" + assert text_data.provider == "Unknown" + +@pytest.mark.parametrize("result, expected_result_color, confidence, expected_confidence_color", [ + ("REAL", "green", 0.85, "green"), # High confidence for REAL result + ("REAL", "green", 0.65, "orange"), # Medium confidence for REAL result + ("REAL", "green", 0.45, "red"), # Low confidence for REAL result + ("FAKE", "red", 0.85, "green"), # High confidence for FAKE result + ("FAKE", "red", 0.65, "orange"), # Medium confidence for FAKE result + ("FAKE", "red", 0.45, "red"), # Low confidence for FAKE result +]) +def test_press_check_button(controller, result, expected_result_color, confidence, expected_confidence_color): + # Mock controller methods and properties + controller.get_text_data = MagicMock(return_value=TextData(text="Sample text")) + text_data = TextData(text="Sample text") + text_data.result = result + text_data.confidence = confidence + text_data.is_fake_news = (result == "FAKE") + controller._predict = MagicMock(return_value=text_data) + controller._add_to_db = MagicMock() + controller.update_provider_list = MagicMock() + controller.rater.get_response = MagicMock(return_value=iter(["Sample response"])) + + # Mock frame and its subcomponents + controller.frame = MagicMock() + controller.frame.result_label = MagicMock() + controller.frame.result_label.configure = MagicMock() + controller.frame.confidence_label = MagicMock() + controller.frame.confidence_label.configure = MagicMock() + controller.frame.output_textbox = MagicMock() + controller.frame.output_textbox.insert = MagicMock() + + # Call the method + controller.press_check_button() + + # Assertions for result label and confidence label colors + controller.frame.result_label.configure.assert_called_with(text=result, fg_color=expected_result_color) + controller.frame.confidence_label.configure.assert_any_call(text=f"{confidence * 100:.2f}%") + controller.frame.confidence_label.configure.assert_any_call(fg_color=expected_confidence_color) + + # Additional assertion to verify that the output textbox is updated + controller.frame.output_textbox.insert.assert_called_with("end", "Sample response") + +def test_predict(controller): + text_data = TextData(text="Sample text") + controller.model_inference.predict.return_value = { + "confidence": 0.9, + "result": "REAL", + "is_fake": False + } + + result = controller._predict(text_data) + + assert result.confidence == 0.9 + assert result.result == "REAL" + assert result.is_fake_news == False + +def test_add_to_db(controller): + # Adjust the fields to match the actual insert_data arguments + text_data = TextData(url="https://example.com", provider="Example Provider", is_fake_news=False) + controller._add_to_db(text_data) + controller.db.insert_data.assert_called_with( + url="https://example.com", + anbieter="example.com", # Adjusted to match actual expected field name + is_fake_news=False + ) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/models/test_data.py b/tests/models/test_data.py new file mode 100644 index 0000000..c54be10 --- /dev/null +++ b/tests/models/test_data.py @@ -0,0 +1,92 @@ +import pytest +from unittest.mock import MagicMock, patch +import os +import sys +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, src_dir) + +from models.data import TextData + +def test_init(): + data = TextData() + assert data.url == "" + assert data.text == "" + assert data.result == "" + assert data.is_fake_news == False + assert data.provider == "" + assert data.confidence is None + assert data._extractor is None + +def test_set_url(): + data = TextData() + url = "https://www.example.com" + data.set_url(url) + assert data.url == url + assert data.text == "" + assert data._extractor is None + +def test_text_from_url_with_url(): + data = TextData() + url = "https://www.example.com" + data.set_url(url) + + # Mock the WebTextExtractor + mock_extractor = MagicMock() + mock_extractor.get_text.return_value = "Example text" + + # Patch the WebTextExtractor import in the TextData module + with patch('models.data.WebTextExtractor', return_value=mock_extractor): + result = data.text_from_url() + + assert result is True + assert data.text == "Example text" + mock_extractor.fetch_content.assert_called_once() + mock_extractor.extract_text.assert_called_once() + mock_extractor.get_text.assert_called_once() + + +def test_text_from_url_without_url(): + data = TextData() + assert data.text_from_url() is False + +def test_get_output(): + data = TextData() + data.result = "Fake" + data.confidence = 0.95 + output = data.get_output() + assert output == "Prediction: Fake Confidence: 0.9500" + +def test_get_provider(): + data = TextData() + url = "https://www.example.com" + data.set_url(url) + assert data.get_provider() == "example.com" + +def test_extract_provider(): + data = TextData() + url = "https://www.example.com" + data.set_url(url) + data.extract_provider() + assert data.provider == "example.com" + +def test_extract_provider_with_invalid_url(): + data = TextData() + url = "invalid_url" + data.set_url(url) + data.extract_provider() + assert data.provider == "Unknown" + +def test__is_valid_url_with_valid_url(): + data = TextData() + url = "https://www.example.com" + assert data._is_valid_url(url) is True + +def test__is_valid_url_with_invalid_url(): + data = TextData() + url = "invalid_url" + assert data._is_valid_url(url) is False + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/models/test_provider.py b/tests/models/test_provider.py new file mode 100644 index 0000000..ac35e83 --- /dev/null +++ b/tests/models/test_provider.py @@ -0,0 +1,35 @@ +import pytest +import sys +import os +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, src_dir) + +from models.provider import Provider +from models.data import TextData # Assuming this is the class used for text_data_list + +def test_provider_init(): + title = "Test Provider" + count = 10 + text_data_list = [TextData(is_fake_news=True), TextData(is_fake_news=False)] + provider = Provider(title, count, text_data_list) + + assert provider.title == title + assert provider.count == count + assert provider.text_data_list == text_data_list + +def test_get_fake_percentage(): + text_data_list = [TextData(is_fake_news=False), TextData(is_fake_news=False), TextData(is_fake_news=True)] + provider = Provider("Test Provider", 10, text_data_list) + + assert provider.get_fake_percentage() == 33.33 + +def test_get_fake_percentage_zero_division(): + text_data_list = [] + provider = Provider("Test Provider", 10, text_data_list) + + assert provider.get_fake_percentage() == 0.0 + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..934ac81 --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,33 @@ +import pytest +import customtkinter +import sys +import os +from unittest.mock import patch + +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src')) +sys.path.insert(0, src_dir) + +from main import Main + +def test_main_initialization(mocker): + # Mocking the MainFrame and MainFrameController to avoid actual UI creation + mocker.patch('main.MainFrame') + mocker.patch('main.MainFrameController') + + # Initialize the Main class + app = Main() + + # Check if the title is set correctly + assert app.title() == "Veracity_AI" + + # Check if the grid configuration is set correctly + assert app.grid_rowconfigure(0)['weight'] == 1 + assert app.grid_columnconfigure(0)['weight'] == 1 + + # Check if the icon is set correctly + assert app.iconpath is not None + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/utils/test_database.py b/tests/utils/test_database.py new file mode 100644 index 0000000..d796638 --- /dev/null +++ b/tests/utils/test_database.py @@ -0,0 +1,62 @@ +import pytest +import sys +import os +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, src_dir) + +from utils.database.database import FakeNewsChecker + +@pytest.fixture(scope="module") +def checker(): + checker = FakeNewsChecker() + yield checker + checker.create_connection().execute('DELETE FROM url_info') + checker.create_connection().close() + +def test_create_table(checker): + conn = checker.create_connection() + + # Inspect the actual table structure + result = conn.execute('PRAGMA table_info(url_info)').fetchall() + actual_columns = [(col[1], col[2], col[2], col[3], col[4], col[5]) for col in result] + + # Compare the actual columns to the expected + expected_columns = [ + ('id', 'INTEGER', 'INTEGER', 1, None, 1), + ('url', 'VARCHAR', 'VARCHAR', 1, None, 0), + ('anbieter', 'VARCHAR', 'VARCHAR', 1, None, 0), + ('is_fake_news', 'BOOLEAN', 'BOOLEAN', 1, None, 0), + ] + + assert actual_columns == expected_columns + + # Clean up the test data + conn.execute('DELETE FROM url_info') + conn.commit() + conn.close() + + +def test_get_next_id(checker): + assert checker.get_next_id() == 1 + +def test_insert_data(checker): + checker.insert_data('https://example.com/news/123', 'Example News', False) + data = checker.fetch_data() + assert len(data) == 1 + assert data[0] == (1, 'https://example.com/news/123', 'Example News', False) + checker.create_connection().execute('DELETE FROM url_info') + checker.create_connection().commit() + +def test_fetch_data(checker): + checker.insert_data('https://example.com/news/123', 'Example News', False) + checker.insert_data('https://fakenews.com/article/456', 'Fake News', True) + data = checker.fetch_data() + assert len(data) == 2 + assert data[0] == (1, 'https://example.com/news/123', 'Example News', False) + assert data[1] == (2, 'https://fakenews.com/article/456', 'Fake News', True) + checker.create_connection().execute('DELETE FROM url_info') + checker.create_connection().commit() + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file diff --git a/tests/utils/test_webText_Extractor.py b/tests/utils/test_webText_Extractor.py new file mode 100644 index 0000000..9274f96 --- /dev/null +++ b/tests/utils/test_webText_Extractor.py @@ -0,0 +1,72 @@ +import unittest.mock +import pytest +import os +import sys +from unittest.mock import MagicMock +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, src_dir) +from utils.webTextExtractor import WebTextExtractor + +@pytest.fixture +def web_text_extractor(): + return WebTextExtractor("https://example.com") + +def test_fetch_content(web_text_extractor): + web_text_extractor.fetch_content() + assert web_text_extractor.content is not None + +def test_extract_text(web_text_extractor): + web_text_extractor.fetch_content() + web_text_extractor.extract_text() + assert web_text_extractor.text is not None + +def test_get_text(web_text_extractor): + # Mock the fetch_content method to set some content + web_text_extractor.fetch_content = MagicMock() + + # Set the content that fetch_content would provide + web_text_extractor.content = "Some content from the webpage" + + # Mock extract_text to simulate its behavior + def mock_extract_text(): + web_text_extractor.text = "Example text" # Simulate the extraction of text + + web_text_extractor.extract_text = MagicMock(side_effect=mock_extract_text) + + # Call the mocked fetch_content method + web_text_extractor.fetch_content() + + # Call the extract_text() method, which will now set the text + web_text_extractor.extract_text() + + # Call the get_text() method + result = web_text_extractor.get_text() + + # Assert that the result is not None + assert result is not None + + # Assert that fetch_content and extract_text were called + web_text_extractor.fetch_content.assert_called_once() + web_text_extractor.extract_text.assert_called_once() + + # Assert that the return value of get_text() is "Example text" + assert result == "Example text" + +def test_resize_article(web_text_extractor): + # Create a long article text for testing + article = " ".join(["This is a test article"] * 600) + resized_article = web_text_extractor.resize_article(article) + + # Check if the resized article has the expected length + assert len(resized_article.split()) == 512 + + # Check if the resized article starts with the 31st word of the original article + assert resized_article.split()[0] == "This" + assert resized_article.split()[1] == "is" + assert resized_article.split()[2] == "a" + assert resized_article.split()[3] == "test" + assert resized_article.split()[4] == "article" + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/tests/views/test_mainScreen.py b/tests/views/test_mainScreen.py new file mode 100644 index 0000000..a664c63 --- /dev/null +++ b/tests/views/test_mainScreen.py @@ -0,0 +1,91 @@ +import pytest +import customtkinter as ctk +import sys +import os +# Add the src directory to the Python path +src_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'src')) +sys.path.insert(0, src_dir) + +from views.mainScreen import MainFrame + +def test_mainframe_initialization(mocker): + # Mocking the controller to avoid actual UI creation + mocker.patch('views.mainScreen.MainFrame') + + # Initialize the MainFrame class + main_frame = MainFrame(None) + + # Check if the grid configuration is set correctly + assert main_frame.grid_rowconfigure(0)['weight'] == 1 + assert main_frame.grid_columnconfigure(0)['weight'] == 1 + assert main_frame.grid_columnconfigure(1)['weight'] == 0 + assert main_frame.grid_columnconfigure(2)['weight'] == 1 + + # Check if the entry_url is created correctly + assert isinstance(main_frame.entry_url, ctk.CTkEntry) + + # Check if the check_button is created correctly + assert isinstance(main_frame.check_button, ctk.CTkButton) + + # Check if the input_textbox is created correctly + assert isinstance(main_frame.input_textbox, ctk.CTkTextbox) + + # Check if the label_frame is created correctly + assert isinstance(main_frame.label_frame, ctk.CTkFrame) + + # Check if the result_label is created correctly + assert isinstance(main_frame.result_label, ctk.CTkLabel) + + # Check if the confidence_label is created correctly + assert isinstance(main_frame.confidence_label, ctk.CTkLabel) + + # Check if the output_textbox is created correctly + assert isinstance(main_frame.output_textbox, ctk.CTkTextbox) + + # Check if the scrollview is created correctly + assert isinstance(main_frame.scrollview, ctk.CTkScrollableFrame) + + # Check if the header is created correctly + assert isinstance(main_frame.header, ctk.CTkLabel) + + # Check if the provider_container is created correctly + assert isinstance(main_frame.provider_container, ctk.CTkFrame) + +def test_set_controller(mocker): + # Mocking the controller to avoid actual UI creation + mocker.patch('views.mainScreen.MainFrame') + + # Initialize the MainFrame class + main_frame = MainFrame(None) + + # Create a mock controller + mock_controller = mocker.Mock() + + # Set the controller + main_frame.set_controller(mock_controller) + + # Check if the controller is set correctly + assert main_frame.controller == mock_controller + +def test_check_button_event(mocker): + # Mocking the controller to avoid actual UI creation + mocker.patch('views.mainScreen.MainFrame') + + # Initialize the MainFrame class + main_frame = MainFrame(None) + + # Create a mock controller + mock_controller = mocker.Mock() + + # Set the controller + main_frame.set_controller(mock_controller) + + # Call the check_button_event method + main_frame.check_button_event() + + # Check if the press_check_button method of the controller is called + mock_controller.press_check_button.assert_called_once() + + +if __name__ == "__main__": + pytest.main([__file__]) \ No newline at end of file