diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 76b2f86..b4e8c93 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.6 +current_version = 0.1.0 commit = False tag = False allow_dirty = True diff --git a/.github/workflows/iris-wallet-desktop.yml b/.github/workflows/iris-wallet-desktop.yml index 17c42c4..5efae76 100644 --- a/.github/workflows/iris-wallet-desktop.yml +++ b/.github/workflows/iris-wallet-desktop.yml @@ -1,49 +1,71 @@ name: Iris Wallet Desktop CI on: - workflow_dispatch: + push: + tags: + - '*' # Trigger this workflow on any tag push jobs: - build-linux: + build-iris-wallet-desktop-linux: runs-on: ubuntu-22.04 steps: - - name: Checkout code with submodules + - name: Checkout repository with submodules uses: actions/checkout@v3 with: - submodules: true - fetch-depth: 1 - submodule-fetch-depth: 1 + submodules: true # Include submodules in the checkout + fetch-depth: 1 # Fetch the latest commit only - - name: Install Rust + - name: Validate Tag and Code Version + run: | + TAG_VERSION=$(git describe --tags) + echo "TAG_VERSION=${TAG_VERSION}" >> $GITHUB_ENV + + # Extract version from tag + echo "Tag version: $TAG_VERSION" + + # Extract version from code + CODE_VERSION=$(grep '__version__' ./src/version.py | awk -F'=' '{print $2}' | tr -d ' "' | xargs) + echo "Code version: $CODE_VERSION" + + # Compare the tag and code version + if [ "$TAG_VERSION" != "$CODE_VERSION" ]; then + echo "❌ Tag version ($TAG_VERSION) does not match code version ($CODE_VERSION)." + exit 1 + else + echo "✅ Tag version matches code version." + fi + + - name: Install Rust Programming Environment run: | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y echo "$HOME/.cargo/bin" >> $GITHUB_PATH source "$HOME/.cargo/env" - - name: Set up Python 3.12.3 + - name: Set up Python 3.12.3 environment uses: actions/setup-python@v5 with: python-version: "3.12.3" - - name: Install dependencies + - name: Install required system dependencies run: | sudo apt update - sudo apt install libxcb-cursor0 -y - sudo apt-get install ruby-dev build-essential && sudo gem i fpm -f + sudo apt install libxcb-cursor0 -y # Required by the application + sudo apt-get install ruby-dev build-essential -y && sudo gem i fpm -f + sudo apt-get update && sudo apt-get install -y libfuse2 # Required for AppImage creation - name: Clone rgb-lightning-node repository with submodules run: git clone https://github.com/RGB-Tools/rgb-lightning-node --recurse-submodules --shallow-submodules - name: Build the rgb-lightning-node binary working-directory: rgb-lightning-node - run: cargo install --debug --path . + run: cargo install --locked --debug --path . - name: Copy rgb-lightning-node binary to root directory run: | mkdir ln_node_binary cp rgb-lightning-node/target/debug/rgb-lightning-node ln_node_binary - - name: Set environment variables from secrets and create config.py + - name: Set environment variables from GitHub Secrets and generate config.py env: CLIENT_ID: ${{ secrets.CLIENT_ID }} PROJECT_ID: ${{ secrets.PROJECT_ID }} @@ -55,45 +77,55 @@ jobs: cd src/utils python generate_config.py - - name: Install python dependencies + - name: Install Python dependencies run: | - pip install poetry - pip install pyinstaller - poetry install + pip install poetry # Dependency management tool + pip install pyinstaller # Required for building executable files + poetry install # Install application dependencies - name: Compile QT resources run: | poetry run pyside6-rcc src/resources.qrc -o src/resources_rc.py - - name: Build the application - run: | - chmod +x build_linux.sh - ./build_linux.sh - - - name: Create AppImage + - name: Create AppImage for Regtest network run: | - chmod +x build_appimage.sh - ./build_appimage.sh ARCH=$(uname -m) - APPIMAGE_NAME="iriswallet-${ARCH}.AppImage" - echo "APPIMAGE_NAME=${APPIMAGE_NAME}" >> $GITHUB_ENV - echo $APPIMAGE_NAME - - - name: Upload Linux artifact + VERSION=$(grep '__version__' src/version.py | cut -d "'" -f 2) + poetry run build-iris-wallet --network=regtest --distribution=appimage + REGTEST_APPIMAGE_NAME="iriswallet-${VERSION}-${ARCH}.AppImage" + RENAME_REGTEST_APPIMAGE_NAME="iriswallet-${VERSION}-regtest-${ARCH}.AppImage" + mv ${REGTEST_APPIMAGE_NAME} ${RENAME_REGTEST_APPIMAGE_NAME} + echo "RENAME_REGTEST_APPIMAGE_NAME=${RENAME_REGTEST_APPIMAGE_NAME}" >> $GITHUB_ENV + chmod +x $RENAME_REGTEST_APPIMAGE_NAME + echo "Generated file: $RENAME_REGTEST_APPIMAGE_NAME" + shell: bash + + - name: Upload Regtest AppImage artifact uses: actions/upload-artifact@v4 with: - name: linux - path: | - iriswallet.deb + name: linux_appimage_regtest + path: ${{ env.RENAME_REGTEST_APPIMAGE_NAME }} - - name: Upload AppImage artifact + - name: Create AppImage for Testnet network + run: | + ARCH=$(uname -m) + VERSION=$(grep '__version__' src/version.py | cut -d "'" -f 2) + poetry run build-iris-wallet --network=testnet --distribution=appimage + TESTNET_APPIMAGE_NAME="iriswallet-${VERSION}-${ARCH}.AppImage" + RENAME_TESTNET_APPIMAGE_NAME="iriswallet-${VERSION}-testnet-${ARCH}.AppImage" + mv ${TESTNET_APPIMAGE_NAME} ${RENAME_TESTNET_APPIMAGE_NAME} + echo "RENAME_TESTNET_APPIMAGE_NAME=${RENAME_TESTNET_APPIMAGE_NAME}" >> $GITHUB_ENV + chmod +x $RENAME_TESTNET_APPIMAGE_NAME + echo "Generated file: $RENAME_TESTNET_APPIMAGE_NAME" + shell: bash + + - name: Upload Testnet AppImage artifact uses: actions/upload-artifact@v4 with: - name: linux_appimage - path: | - ${{ env.APPIMAGE_NAME }} + name: linux_appimage_testnet + path: ${{ env.RENAME_TESTNET_APPIMAGE_NAME }} - build-macos: + build-iris-wallet-desktop-macos: runs-on: macos-latest steps: - name: Checkout code with submodules @@ -101,7 +133,26 @@ jobs: with: submodules: true fetch-depth: 1 - submodule-fetch-depth: 1 + + - name: Validate Tag and Code Version + run: | + TAG_VERSION=$(git describe --tags) + echo "TAG_VERSION=${TAG_VERSION}" >> $GITHUB_ENV + + # Extract version from tag + echo "Tag version: $TAG_VERSION" + + # Extract version from code + CODE_VERSION=$(grep '__version__' ./src/version.py | awk -F'=' '{print $2}' | tr -d ' "' | xargs) + echo "Code version: $CODE_VERSION" + + # Compare the tag and code version + if [ "$TAG_VERSION" != "$CODE_VERSION" ]; then + echo "❌ Tag version ($TAG_VERSION) does not match code version ($CODE_VERSION)." + exit 1 + else + echo "✅ Tag version matches code version." + fi - name: Install Rust run: | @@ -119,7 +170,7 @@ jobs: - name: Build the rgb-lightning-node binary working-directory: rgb-lightning-node - run: cargo install --debug --path . + run: cargo install --locked --debug --path . - name: Copy rgb-lightning-node binary to root directory run: | @@ -150,195 +201,99 @@ jobs: - name: Build the application run: | - chmod +x build_macos.sh - ./build_macos.sh + # Build the regtest application for macOS + poetry run build-iris-wallet --network=regtest + + # Build the testnet application for macOS + poetry run build-iris-wallet --network=testnet - name: Upload macOS artifact uses: actions/upload-artifact@v4 with: name: macos - path: iriswallet* - - build-windows: - runs-on: windows-latest - steps: - - name: Checkout code with submodules - uses: actions/checkout@v3 - with: - submodules: true - fetch-depth: 1 - submodule-fetch-depth: 1 - - - name: Install Rust - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - - - name: Set up Python 3.12 - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - name: Clone rgb-lightning-node repository with submodules - run: git clone https://github.com/RGB-Tools/rgb-lightning-node --recurse-submodules --shallow-submodules - - - name: Build the rgb-lightning-node binary - working-directory: rgb-lightning-node - run: cargo install --debug --path . - - - name: Copy rgb-lightning-node binary to root directory - run: | - mkdir ln_node_binary - copy rgb-lightning-node\\target\\debug\\rgb-lightning-node.exe ln_node_binary - - - name: Generate config.py with secrets and inno Setup script with dynamic version - env: - CLIENT_ID: ${{ secrets.CLIENT_ID }} - PROJECT_ID: ${{ secrets.PROJECT_ID }} - AUTH_URI: ${{ secrets.AUTH_URI }} - TOKEN_URI: ${{ secrets.TOKEN_URI }} - AUTH_PROVIDER_CERT_URL: ${{ secrets.AUTH_PROVIDER_CERT_URL }} - CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }} - run: | - cd src/utils - python generate_config.py - python generate_iss.py - - - name: Install python dependencies - run: | - pip install poetry - pip install pyinstaller - poetry install + path: iriswallet-* - - name: Compile QT resources and build exe - run: | - poetry run pyside6-rcc src/resources.qrc -o src/resources_rc.py - poetry run pyinstaller iris_wallet_desktop.spec - - - name: Build the application - uses: Minionguyjpro/Inno-Setup-Action@v1.2.2 - with: - path: updated_iriswallet.iss - - - name: Upload Windows artifact - uses: actions/upload-artifact@v4 - with: - name: windows - path: iriswallet.exe - - - upload-release: - if: needs.build-linux.result == 'success' || needs.build-macos.result == 'success' || needs.build-windows.result == 'success' - runs-on: ubuntu-latest - needs: [build-linux, build-macos, build-windows] + release-artifacts: + if: needs.build-iris-wallet-desktop-macos.result == 'success' && needs.build-iris-wallet-desktop-linux.result == 'success' + runs-on: ubuntu-24.04 + needs: [build-iris-wallet-desktop-macos, build-iris-wallet-desktop-linux] permissions: contents: write steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Read version from VERSION.py + - name: Retrieve tag name run: | - VERSION=$(grep '__version__' src/version.py | cut -d "'" -f 2) - echo "TAG_NAME=v${VERSION}" >> $GITHUB_ENV - echo "RELEASE_NAME=Release v${VERSION}" >> $GITHUB_ENV - ARCH=$(uname -m) - APPIMAGE_NAME="iriswallet-${ARCH}.AppImage" - echo "APPIMAGE_NAME=${APPIMAGE_NAME}" >> $GITHUB_ENV - echo $APPIMAGE_NAME - APPNAME_MAC="iriswallet ${VERSION}".dmg - echo "APPNAME_MAC=${APPNAME_MAC}" >> $GITHUB_ENV - echo $APPNAME_MAC - - - - name: Create GitHub Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: "${{ env.TAG_NAME }}" - release_name: "${{ env.RELEASE_NAME }}" - draft: false - prerelease: false + TAG_NAME=$(git describe --tags) + echo "TAG_NAME=${TAG_NAME}" >> $GITHUB_ENV + echo "Using tag: $TAG_NAME" - - name: Create uploads folder + - name: Prepare uploads folder run: mkdir -p ./uploads - - name: Download Linux artifact - uses: actions/download-artifact@v4 - with: - name: linux # Name of the Linux artifact - path: ./uploads # Destination path folder - - - name: Download Linux AppImage artifact - uses: actions/download-artifact@v4 - with: - name: linux_appimage # Name of the Linux artifact - path: ./uploads # Destination path folder - name: Download macOS artifact uses: actions/download-artifact@v4 with: name: macos # Name of the macOS artifact path: ./uploads # Destination path folder - - name: Download windows artifact + - name: Download Testnet AppImage artifact uses: actions/download-artifact@v4 with: - name: windows # Name of the windows artifact - path: ./uploads # Destination path folder + name: linux_appimage_testnet + path: ./uploads - - name: Upload Release Artifact Linux DEB - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Download Regtest AppImage artifact + uses: actions/download-artifact@v4 with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./uploads/iriswallet.deb - asset_name: iris-wallet-desktop-linux-"${{ env.TAG_NAME }}".deb - asset_content_type: application/vnd.debian.binary-package + name: linux_appimage_regtest + path: ./uploads - - name: Upload Release Artifact Linux AppImage - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./uploads/${{ env.APPIMAGE_NAME }} - asset_name: iris-wallet-desktop-"${{ env.TAG_NAME }}".AppImage - asset_content_type: application/octet-stream + - name: Extract Version and Artifacts Info + run: | + VERSION=$(grep '__version__' src/version.py | cut -d "'" -f 2) - - name: Upload Release Artifact windows - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./uploads/iriswallet.exe - asset_name: iris-wallet-desktop-windows-"${{ env.TAG_NAME }}".exe - asset_content_type: application/vnd.microsoft.portable-executable + echo "TESTNET_APPIMAGE_NAME=${TESTNET_APPIMAGE_NAME}" >> $GITHUB_ENV + echo "REGTEST_APPIMAGE_NAME=${REGTEST_APPIMAGE_NAME}" >> $GITHUB_ENV + + cd uploads + REGTEST_APPNAME_MAC=$(ls iriswallet-${VERSION}-regtest-*.dmg 2>/dev/null || echo "") + TESTNET_APPNAME_MAC=$(ls iriswallet-${VERSION}-testnet-*.dmg 2>/dev/null || echo "") + + TESTNET_APPIMAGE_NAME=$(ls iriswallet-${VERSION}-regtest-*.AppImage 2>/dev/null || echo "") + REGTEST_APPIMAGE_NAME=$(ls iriswallet-${VERSION}-testnet-*.AppImage 2>/dev/null || echo "") + + echo "REGTEST_APPNAME_MAC=${REGTEST_APPNAME_MAC}" >> $GITHUB_ENV + echo "TESTNET_APPNAME_MAC=${TESTNET_APPNAME_MAC}" >> $GITHUB_ENV + echo "REGTEST_APPIMAGE_NAME=${REGTEST_APPIMAGE_NAME}" >> $GITHUB_ENV + echo "TESTNET_APPIMAGE_NAME=${TESTNET_APPIMAGE_NAME}" >> $GITHUB_ENV - - name: Upload Release Artifact macOS - uses: actions/upload-release-asset@v1 + - name: Create GitHub Release and Upload Assets + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ env.TAG_NAME }} + files: | + ./uploads/${{ env.REGTEST_APPNAME_MAC }} + ./uploads/${{ env.TESTNET_APPNAME_MAC }} + ./uploads/${{ env.TESTNET_APPIMAGE_NAME }} + ./uploads/${{ env.REGTEST_APPIMAGE_NAME }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: ./uploads/${{ env.APPNAME_MAC }} - asset_name: iris-wallet-desktop-macos-"${{ env.TAG_NAME }}".dmg - asset_content_type: application/octet-stream - cleanup: + cleanup-artifacts: if: always() - runs-on: ubuntu-latest - needs: [build-linux, build-macos, build-windows,upload-release] + runs-on: ubuntu-22.04 + needs: [build-iris-wallet-desktop-macos, build-iris-wallet-desktop-linux, release-artifacts] steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Cleanup Artifacts + - name: Delete Artifacts from Workflow Run uses: geekyeggo/delete-artifact@v5 with: name: | - linux - linux_appimage macos - windows + linux_appimage_regtest + linux_appimage_testnet failOnError: false diff --git a/README.md b/README.md index 81844dd..7c3c859 100644 --- a/README.md +++ b/README.md @@ -91,14 +91,6 @@ client_config = { 'client_secret': 'your_client_secret', }, } - -# Config for the error report email server -report_email_server_config = { - 'email_id': 'your_email_id', - 'email_token': 'your_email_token', - 'smtp_host': 'smtp.gmail.com', - 'smtp_port': '587' -} ``` #### 8.2 Create Google Drive Credentials @@ -138,9 +130,6 @@ report_email_server_config = { 6. **Update Your Configuration:** - Save the modified JSON file and add it to your `config.py` file. -#### 8.3 Create Email Server Configuration -Search for **App Passwords** in your Gmail account to create an app password for email access. - ### 9. Start the Application You can now start the Iris Wallet application using: ```bash diff --git a/build_macos.sh b/build_macos.sh index 58d06e9..b176d5d 100755 --- a/build_macos.sh +++ b/build_macos.sh @@ -3,6 +3,8 @@ # Getting the app name and version from version.py and constant.py. APP_NAME=$(grep 'APP_NAME' ./src/utils/constant.py | awk -F'=' '{print $2}' | tr -d ' "' | xargs) VERSION=$(grep '__version__' ./src/version.py | awk -F'=' '{print $2}' | tr -d ' "' | xargs) +BITCOIN_NETWORK=$(grep '__network__' ./src/flavour.py | awk -F'=' '{print $2}' | tr -d ' "' | xargs) +ARCH=$(uname -m) # Check if APP_NAME and VERSION are non-empty @@ -33,9 +35,23 @@ ls -l "${DIST_PATH}" if [ -d "${APP_BUNDLE}" ]; then echo "App bundle created successfully." echo "Creating DMG file..." + + # Create the DMG and capture the output directory npx create-dmg --dmg-title="${APP_NAME}-${VERSION}" "${APP_BUNDLE}" - echo "DMG file created successfully." + # Locate the newly created DMG file in the current directory + DMG_FILE=$(ls -1t *.dmg | head -n 1) + + if [ -f "${DMG_FILE}" ]; then + echo "DMG file created: ${DMG_FILE}" + + # Rename the DMG file + NEW_DMG_NAME="${APP_NAME}-${VERSION}-${BITCOIN_NETWORK}-${ARCH}.dmg" + mv "${DMG_FILE}" "${NEW_DMG_NAME}" + echo "DMG file renamed to: ${NEW_DMG_NAME}" + else + echo "Failed to locate the DMG file." + fi echo "Removing app bundle..." rm -rf "${DIST_PATH}" diff --git a/pyproject.toml b/pyproject.toml index 97f6708..0cea446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "iris-wallet-desktop" -version = "0.0.6" +version = "0.1.0" description = "" readme = "README.md" license = "" diff --git a/src/translations/en_IN.qm b/src/translations/en_IN.qm index 0ee3844..47bca71 100644 Binary files a/src/translations/en_IN.qm and b/src/translations/en_IN.qm differ diff --git a/src/translations/en_IN.ts b/src/translations/en_IN.ts index d49474b..42f9bed 100644 --- a/src/translations/en_IN.ts +++ b/src/translations/en_IN.ts @@ -1510,5 +1510,29 @@ If you understand the above remarks and wish to proceed, press the button below set_minimum_confirmation_desc Set a minimum confirmation for RGB operation + + github_issue_label + GitHub Issue + + + email_us_at + Email us at: + + + do_not_expect_reply + Note: do not expect a reply. + + + thank_you_for_your_support + Thank you for your support! + + + open_an_issue + Open an issue on GitHub (preferred): + + + please_help_us + Please help us improve by sharing the logs. You can report the issue in one of the following ways: + diff --git a/src/utils/common_utils.py b/src/utils/common_utils.py index 8a774b5..925ff2e 100644 --- a/src/utils/common_utils.py +++ b/src/utils/common_utils.py @@ -7,17 +7,10 @@ import base64 import binascii import os -import platform import shutil -import smtplib import time import zipfile -from concurrent.futures import ThreadPoolExecutor from datetime import datetime -from email import encoders -from email.mime.base import MIMEBase -from email.mime.multipart import MIMEMultipart -from email.mime.text import MIMEText from io import BytesIO import pydenticon @@ -42,7 +35,6 @@ from PySide6.QtWidgets import QPlainTextEdit from PySide6.QtWidgets import QWidget -from config import report_email_server_config from src.data.repository.setting_repository import SettingRepository from src.data.service.helpers.main_asset_page_helper import get_offline_asset_ticker from src.model.enums.enums_model import AssetType @@ -61,9 +53,7 @@ from src.utils.constant import SLOW_TRANSACTION_FEE_BLOCKS from src.utils.custom_exception import CommonException from src.utils.error_message import ERROR_SAVE_LOGS -from src.utils.error_message import ERROR_SEND_REPORT_EMAIL from src.utils.error_message import ERROR_SOMETHING_WENT_WRONG -from src.utils.error_message import ERROR_TITLE from src.utils.info_message import INFO_COPY_MESSAGE from src.utils.info_message import INFO_LOG_SAVE_DESCRIPTION from src.utils.ln_node_manage import LnNodeServerManager @@ -472,108 +462,6 @@ def close_button_navigation(parent, back_page_navigation=None): parent.view_model.page_navigation.settings_page() -def generate_error_report_email(url, title): - """Collect system info, format it, and generate the email body.""" - # Collect system information - network = SettingRepository.get_wallet_network() - system_info = { - 'URL': url, - 'OS': platform.system(), - 'OS Version': platform.version(), - 'Wallet Version': __version__, - 'Wallet Network': network.value, - 'Architecture': platform.machine(), - 'Processor': platform.processor(), - } - - # Format system information for the email report - system_info_formatted = ( - f"System Information Report:\n" - f"-------------------------\n" - f"URL: {system_info['URL']}\n" - f"Operating System: {system_info['OS']}\n" - f"OS Version: {system_info['OS Version']}\n" - f"Wallet Version: {system_info['Wallet Version']}\n" - f"Wallet Network: {system_info['Wallet Network']}\n" - f"Architecture: {system_info['Architecture']}\n" - f"Processor: {system_info['Processor']}\n" - ) - - # Generate the email body - email_body = ( - f"{title}\n" - f"{'=' * len(title)}\n\n" - f"{system_info_formatted}\n" - f"Attached logs can be found in the provided ZIP file for further details." - ) - - return email_body - - -def send_crash_report_async(email_to, subject, body, zip_file_path): - """ - Asynchronously sends a crash report email with a ZIP attachment. - - Initializes a thread pool to run the email sending task asynchronously. - """ - - def send_crash_report(email_to, subject, body, zip_file_path): - """ - - Retrieves the email server configuration (email ID and token). - - Creates a multipart email message with the subject, sender, and recipient. - - Attaches the email body and the ZIP file (if it exists). - - Connects to the SMTP server using TLS, authenticates with the email ID and token, and sends the email. - - Handles any exceptions that occur during the email sending process by showing an error toast message. - """ - email_id = report_email_server_config['email_id'] - email_token = report_email_server_config['email_token'] - - # Create a multipart message - msg = MIMEMultipart() - msg['Subject'] = subject - msg['From'] = email_id - msg['To'] = email_to - - # Attach the body text - msg.attach(MIMEText(body)) - - # Attach the ZIP file - if zip_file_path and os.path.exists(zip_file_path): - with open(zip_file_path, 'rb') as attachment: - part = MIMEBase('application', 'octet-stream') - part.set_payload(attachment.read()) - - # Encode the attachment - encoders.encode_base64(part) - - # Add headers - part.add_header( - 'Content-Disposition', - f'attachment; filename="{os.path.basename(zip_file_path)}"', - ) - - # Attach the part to the message - msg.attach(part) - - try: - # Connect to the SMTP server - smtp_host = report_email_server_config.get( - 'smtp_host', 'smtp.gmail.com', - ) - smtp_port = report_email_server_config.get('smtp_port', 587) - with smtplib.SMTP(smtp_host, smtp_port) as server: - server.starttls() - server.login(email_id, email_token) - server.sendmail(email_id, [email_to], msg.as_string()) - except CommonException as e: - ToastManager.error( - parent=None, title=ERROR_TITLE, - description=ERROR_SEND_REPORT_EMAIL.format(e), - ) - - executor = ThreadPoolExecutor(max_workers=1) - executor.submit(send_crash_report, email_to, subject, body, zip_file_path) - - def find_files_with_name(path, keyword): """This method finds a file using the provided name.""" found_files = [] diff --git a/src/utils/constant.py b/src/utils/constant.py index 750f5cc..dabdc0a 100644 --- a/src/utils/constant.py +++ b/src/utils/constant.py @@ -66,10 +66,10 @@ BITCOIND_RPC_USER_REGTEST = 'user' BITCOIND_RPC_PASSWORD_REGTEST = 'password' -BITCOIND_RPC_HOST_REGTEST = 'localhost' -BITCOIND_RPC_PORT_REGTEST = 18443 -INDEXER_URL_REGTEST = '127.0.0.1:50001' -PROXY_ENDPOINT_REGTEST = 'rpc://127.0.0.1:3000/json-rpc' +BITCOIND_RPC_HOST_REGTEST = 'regtest-bitcoind.rgbtools.org' +BITCOIND_RPC_PORT_REGTEST = 80 +INDEXER_URL_REGTEST = 'electrum.rgbtools.org:50041' +PROXY_ENDPOINT_REGTEST = 'rpcs://proxy.iriswallet.com/0.2/json-rpc' LDK_DATA_NAME_REGTEST = 'dataldkregtest' BITCOIND_RPC_USER_TESTNET = 'user' @@ -118,3 +118,7 @@ # Syncing chain info label timer in milliseconds SYNCING_CHAIN_LABEL_TIMER = 5000 + +# Email and github issue url for error report +CONTACT_EMAIL = 'iriswalletdesktop@gmail.com' +GITHUB_ISSUE_LINK = 'https://github.com/RGB-Tools/iris-wallet-desktop/issues/new?template=Blank+issue' diff --git a/src/utils/error_message.py b/src/utils/error_message.py index 0e8a097..e301d92 100644 --- a/src/utils/error_message.py +++ b/src/utils/error_message.py @@ -46,7 +46,6 @@ ERROR_ENDPOINT_NOT_ALLOW_TO_CACHE_CHECK_CACHE_LIST = 'Provide endpoint {} not allow to cache please check cache list in endpoints.py file' ERROR_CREATE_UTXO_FEE_RATE_ISSUE = 'Unexpected error' ERROR_MESSAGE_TO_CHANGE_FEE_RATE = 'Please change default fee rate from setting page' -ERROR_SEND_REPORT_EMAIL = 'Failed to send email: {}' ERROR_OPERATION_CANCELLED = 'Operation cancelled' ERROR_NEW_CONNECTION_ERROR = 'Failed to connect to the server.because of network connection.' ERROR_MAX_RETRY_EXITS = 'Max retries exceeded. while checking internet connection.' diff --git a/src/utils/generate_config.py b/src/utils/generate_config.py index c3d06a5..91404fb 100644 --- a/src/utils/generate_config.py +++ b/src/utils/generate_config.py @@ -12,13 +12,33 @@ import os + +def get_env_var(key, default=None): + """ + Returns the value of an environment variable, or a default value if the variable + is not set or is an empty string. + + Args: + key (str): The name of the environment variable. + default (Any): The fallback value if the variable is not set or is empty. + + Returns: + Any: The environment variable's value or the default. + """ + value = os.getenv(key, default) + # If the value is an empty string, return the default + return value if value else default + + # Read environment variables -client_id = os.getenv('CLIENT_ID') -project_id = os.getenv('PROJECT_ID') -auth_uri = os.getenv('AUTH_URI') -token_uri = os.getenv('TOKEN_URI') -auth_provider_cert_url = os.getenv('AUTH_PROVIDER_CERT_URL') -client_secret = os.getenv('CLIENT_SECRET') +client_id = get_env_var('CLIENT_ID') +project_id = get_env_var('PROJECT_ID') +auth_uri = get_env_var('AUTH_URI', 'https://accounts.google.com/o/oauth2/auth') +token_uri = get_env_var('TOKEN_URI', 'https://oauth2.googleapis.com/token') +auth_provider_cert_url = get_env_var( + 'AUTH_PROVIDER_CERT_URL', 'https://www.googleapis.com/oauth2/v1/certs', +) +client_secret = get_env_var('CLIENT_SECRET') # Create the content of config.py config_content = f""" diff --git a/src/utils/handle_exception.py b/src/utils/handle_exception.py index bd0eaee..bc9413c 100644 --- a/src/utils/handle_exception.py +++ b/src/utils/handle_exception.py @@ -38,7 +38,7 @@ def handle_exceptions(exc): ) if exc.response.status_code == 500: - PageNavigationEventManager.get_instance().error_report_signal.emit(exc.response.url) + PageNavigationEventManager.get_instance().error_report_signal.emit() raw_exc = exc.response.json() raise CommonException(error_message, raw_exc) from exc diff --git a/src/utils/page_navigation.py b/src/utils/page_navigation.py index f4e401b..9a0e084 100644 --- a/src/utils/page_navigation.py +++ b/src/utils/page_navigation.py @@ -411,7 +411,7 @@ def sidebar(self): """This method return the sidebar objects.""" return self._ui.sidebar - def error_report_dialog_box(self, url): + def error_report_dialog_box(self): """This method display the error report dialog box""" - error_report_dialog = ErrorReportDialog(url=url) + error_report_dialog = ErrorReportDialog() error_report_dialog.exec() diff --git a/src/utils/page_navigation_events.py b/src/utils/page_navigation_events.py index 8916b72..e63c292 100644 --- a/src/utils/page_navigation_events.py +++ b/src/utils/page_navigation_events.py @@ -1,3 +1,4 @@ +# pylint: disable = too-few-public-methods """Make page navigation global""" from __future__ import annotations @@ -45,7 +46,7 @@ class PageNavigationEventManager(QObject): about_page_signal = Signal() faucets_page_signal = Signal() help_page_signal = Signal() - error_report_signal = Signal(str) + error_report_signal = Signal() def __init__(self): super().__init__() diff --git a/src/version.py b/src/version.py index 7299b46..edadef9 100644 --- a/src/version.py +++ b/src/version.py @@ -6,4 +6,4 @@ """ from __future__ import annotations -__version__ = '0.0.6' +__version__ = '0.1.0' diff --git a/src/views/components/error_report_dialog_box.py b/src/views/components/error_report_dialog_box.py index 2fb85e9..b6900d6 100644 --- a/src/views/components/error_report_dialog_box.py +++ b/src/views/components/error_report_dialog_box.py @@ -1,106 +1,235 @@ -""" -This module defines the ErrorReportDialog class, which represents a message box -for sending error reports with translations and error details. -""" +# pylint: disable=too-many-instance-attributes, too-many-statements +"""This module contains the ErrorReportDialog class which contains the UI for the error report dialog box""" from __future__ import annotations -import shutil - from PySide6.QtCore import QCoreApplication -from PySide6.QtWidgets import QMessageBox +from PySide6.QtCore import QSize +from PySide6.QtCore import Qt +from PySide6.QtGui import QCursor +from PySide6.QtGui import QIcon +from PySide6.QtWidgets import QDialog +from PySide6.QtWidgets import QDialogButtonBox +from PySide6.QtWidgets import QFileDialog +from PySide6.QtWidgets import QGridLayout +from PySide6.QtWidgets import QHBoxLayout +from PySide6.QtWidgets import QLabel +from PySide6.QtWidgets import QPushButton +from PySide6.QtWidgets import QSizePolicy +from PySide6.QtWidgets import QSpacerItem +from PySide6.QtWidgets import QVBoxLayout -from config import report_email_server_config -from src.utils.common_utils import generate_error_report_email -from src.utils.common_utils import send_crash_report_async +from src.utils.common_utils import copy_text +from src.utils.common_utils import download_file from src.utils.common_utils import zip_logger_folder -from src.utils.error_message import ERROR_OPERATION_CANCELLED -from src.utils.info_message import INFO_SENDING_ERROR_REPORT +from src.utils.constant import CONTACT_EMAIL +from src.utils.constant import GITHUB_ISSUE_LINK +from src.utils.helpers import load_stylesheet from src.utils.local_store import local_store -from src.version import __version__ -from src.views.components.toast import ToastManager -class ErrorReportDialog(QMessageBox): - """This class represents the error report dialog in the application.""" +class ErrorReportDialog(QDialog): + """This class represents the UI elements of the error report dialog""" - def __init__(self, url, parent=None): - """Initialize the ErrorReportDialog message box with translated strings and error details.""" - super().__init__(parent) - self.url = url - self.setWindowTitle('Send Error Report') - # Create the message box - self.setIcon(QMessageBox.Critical) - self.setWindowTitle( - QCoreApplication.translate( - 'iris_wallet_desktop', 'error_report', None, + def __init__(self): + super().__init__() + + self.setObjectName('error_report_dialog_box') + self.setStyleSheet( + load_stylesheet( + 'views/qss/error_report_dialog.qss', ), ) + self.grid_layout = QGridLayout(self) + self.grid_layout.setObjectName('grid_layout') + self.grid_layout.setContentsMargins(-1, -1, 33, 25) + self.icon_label = QLabel(self) + self.icon_label.setObjectName('icon_label') + icon = QIcon.fromTheme('dialog-error') + self.icon_label.setPixmap(icon.pixmap(70, 70)) + + self.grid_layout.addWidget( + self.icon_label, 0, 0, 1, 1, Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter, + ) + + self.were_sorry_label = QLabel(self) + self.were_sorry_label.setObjectName('were_sorry_label') + self.were_sorry_label.setContentsMargins(0, 15, 2, 0) + self.grid_layout.addWidget(self.were_sorry_label, 0, 1, 1, 2) + + self.help_us_label = QLabel(self) + self.help_us_label.setObjectName('help_us_label') + self.help_us_label.setWordWrap(True) + + self.grid_layout.addWidget(self.help_us_label, 1, 1, 1, 2) + + self.open_issue_label = QLabel(self) + self.open_issue_label.setObjectName('open_issue_label') + + self.grid_layout.addWidget(self.open_issue_label, 2, 1, 1, 1) + + self.github_issue_label = QLabel(self) + self.github_issue_label.setObjectName('github_issue_label') + + self.github_issue_label.setOpenExternalLinks(True) + + self.grid_layout.addWidget(self.github_issue_label, 2, 2, 1, 1) - # Fetch translations - self.text_sorry = QCoreApplication.translate( - 'iris_wallet_desktop', 'something_went_wrong_mb', None, + self.vertical_layout = QVBoxLayout() + self.vertical_layout.setObjectName('vertical_layout') + self.vertical_layout.setSpacing(0) + self.horizontal_layout = QHBoxLayout() + self.horizontal_layout.setObjectName('horizontal_layout') + self.horizontal_layout.setContentsMargins(-1, -1, 10, -1) + self.email_us_label = QLabel(self) + self.email_us_label.setObjectName('email_us_label') + self.email_us_label.setMaximumSize(QSize(100, 16777215)) + + self.horizontal_layout.addWidget(self.email_us_label) + + self.email_label = QLabel(self) + self.email_label.setObjectName('email_label') + self.email_label.setText(CONTACT_EMAIL) + + self.horizontal_layout.addWidget( + self.email_label, Qt.AlignmentFlag.AlignHCenter, ) - self.text_help = QCoreApplication.translate( - 'iris_wallet_desktop', 'error_description_mb', None, + + self.copy_button = QPushButton(self) + self.copy_button.setObjectName('copy_button') + self.copy_button.setMaximumSize(QSize(16, 16)) + self.copy_button.setStyleSheet('background:none') + icon = QIcon() + icon.addFile( + ':/assets/copy.png', QSize(), + QIcon.Mode.Normal, QIcon.State.Off, ) - self.text_included = QCoreApplication.translate( - 'iris_wallet_desktop', 'what_will_be_included', None, + self.copy_button.setIcon(icon) + self.copy_button.setFlat(True) + self.copy_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) + + self.horizontal_layout.addWidget( + self.copy_button, Qt.AlignmentFlag.AlignLeft, ) - self.text_error_details = QCoreApplication.translate( - 'iris_wallet_desktop', 'error_details_title', None, + + self.horizontal_spacer = QSpacerItem( + 20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed, ) - self.text_app_version = QCoreApplication.translate( - 'iris_wallet_desktop', 'application_version', None, + + self.horizontal_layout.addItem(self.horizontal_spacer) + + self.vertical_layout.addLayout(self.horizontal_layout) + + self.no_reply_label = QLabel(self) + self.no_reply_label.setObjectName('no_reply_label') + self.vertical_layout.addWidget( + self.no_reply_label, Qt.AlignmentFlag.AlignTop, ) - self.text_os_info = QCoreApplication.translate( - 'iris_wallet_desktop', 'os_info', None, + + self.grid_layout.addLayout(self.vertical_layout, 3, 1, 1, 2) + + self.vertical_spacer = QSpacerItem( + 20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum, ) - self.text_send_report = QCoreApplication.translate( - 'iris_wallet_desktop', 'error_report_permission', None, + + self.grid_layout.addItem(self.vertical_spacer, 4, 1) + + self.thank_you_label = QLabel(self) + self.thank_you_label.setObjectName('thank_you_label') + + self.grid_layout.addWidget(self.thank_you_label, 5, 1, 1, 2) + + self.vertical_spacer_2 = QSpacerItem( + 20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum, ) - # Set the text for the message box - self.setText(self.text_sorry) - self.setInformativeText( - f"{self.text_help}\n\n" - f"{self.text_included}\n" - f"{self.text_error_details}\n" - f"{self.text_app_version}\n" - f"{self.text_os_info}\n\n" - f"{self.text_send_report}", + self.grid_layout.addItem(self.vertical_spacer_2, 6, 1) + self.button_box = QDialogButtonBox(self) + self.button_box.setObjectName('buttonBox') + self.button_box.setOrientation(Qt.Orientation.Horizontal) + + self.download_debug_logs = QPushButton(self) + self.download_debug_logs.setObjectName('download_debug_logs') + self.download_debug_logs.setMinimumSize(QSize(120, 40)) + self.download_debug_logs.setCursor( + QCursor(Qt.CursorShape.PointingHandCursor), + ) + self.button_box.addButton( + self.download_debug_logs, QDialogButtonBox.ActionRole, ) - self.setStandardButtons(QMessageBox.Yes | QMessageBox.No) - self.setDefaultButton(QMessageBox.Yes) - # Connect the buttonClicked signal to a custom slot - self.buttonClicked.connect(self.on_button_clicked) + self.grid_layout.addWidget(self.button_box, 7, 1, 1, 2) - def on_button_clicked(self, button): - """ - Handles the button click event to either send an error report or cancel the operation. + self.retranslate_ui() + self.setup_ui() + + def retranslate_ui(self): + """translations for the error report dialog""" + self.setWindowTitle( + QCoreApplication.translate( + 'iris_wallet_desktop', 'error_report', None, + ), + ) + self.were_sorry_label.setText( + QCoreApplication.translate( + 'iris_wallet_desktop', 'something_went_wrong_mb', None, + ), + ) + self.help_us_label.setText( + QCoreApplication.translate( + 'iris_wallet_desktop', 'please_help_us', None, + ), + ) + self.open_issue_label.setText( + QCoreApplication.translate( + 'iris_wallet_desktop', 'open_an_issue', None, + ), + ) + self.github_issue_label.setText( + f""" + { + QCoreApplication.translate( + "iris_wallet_desktop", "github_issue_label", None + ) + } + """, + ) + self.email_us_label.setText( + QCoreApplication.translate( + 'iris_wallet_desktop', 'email_us_at', None, + ), + ) + self.no_reply_label.setText(f"({ + QCoreApplication.translate( + "iris_wallet_desktop", "do_not_expect_reply", None + ) + })") + self.thank_you_label.setText( + QCoreApplication.translate( + 'iris_wallet_desktop', 'thank_you_for_your_support', None, + ), + ) + self.download_debug_logs.setText( + QCoreApplication.translate( + 'iris_wallet_desktop', 'download_debug_log', None, + ), + ) - If the 'Yes' button is clicked: - - Shows an info toast notification indicating the report is being sent. - - Compresses the log files into a ZIP archive. - - Prepares an error report email with the appropriate subject and body. - - Sends the error report asynchronously to the specified email ID. + def setup_ui(self): + """Ui connections for error report dialog""" + self.copy_button.clicked.connect(lambda: copy_text(self.email_label)) + self.download_debug_logs.clicked.connect( + self.on_click_download_debug_log, + ) - If the 'No' button is clicked: - - Shows a warning toast notification indicating the operation was cancelled. - """ - if button == self.button(QMessageBox.Yes): - ToastManager.info(INFO_SENDING_ERROR_REPORT) + def on_click_download_debug_log(self): + """This method opens the file dialog box and saves the debug logs to the selected path""" - base_path = local_store.get_path() - _, output_dir = zip_logger_folder(base_path) - zip_file_path = shutil.make_archive(output_dir, 'zip', output_dir) + path = local_store.get_path() + zip_filename, output_dir = zip_logger_folder(path) - # Set the subject and formatted body - subject = f"Iris Wallet Error Report - Version {__version__}" - title = 'Error Report for Iris Wallet Desktop' - body = generate_error_report_email(url=self.url, title=title) - email_id = report_email_server_config['email_id'] + save_path, _ = QFileDialog.getSaveFileName( + self, 'Save logs File', zip_filename, 'Zip Files (*.zip)', + ) - send_crash_report_async(email_id, subject, body, zip_file_path) - elif button == self.button(QMessageBox.No): - ToastManager.warning(ERROR_OPERATION_CANCELLED) + if save_path: + download_file(save_path, output_dir) diff --git a/src/views/components/transaction_detail_frame.py b/src/views/components/transaction_detail_frame.py index a923b0e..1b2e3e3 100644 --- a/src/views/components/transaction_detail_frame.py +++ b/src/views/components/transaction_detail_frame.py @@ -93,6 +93,7 @@ def set_frame(self): ) self.transaction_amount = QLabel(self) + self.transaction_amount.setFixedHeight(18) self.transaction_amount.setObjectName('label_15') self.transaction_amount.setStyleSheet( 'font: 15px "Inter";\n' diff --git a/src/views/qss/error_report_dialog.qss b/src/views/qss/error_report_dialog.qss new file mode 100644 index 0000000..f08b1e6 --- /dev/null +++ b/src/views/qss/error_report_dialog.qss @@ -0,0 +1,23 @@ +/* Styles for the logo title text */ +QLabel { + font: 16px "Inter"; + color: white; +} + +QLabel#email_label{ + padding-left:5px; +} + +QLabel#icon_label{ + padding-top: 20px; + padding-left: 10px; + padding-right: 20px; +} + +QPushButton#download_debug_logs{ + font:16px "Inter"; +} + +QLabel#no_reply_label{ + font:italic; +} diff --git a/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py b/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py index 9e7a5f7..f21d772 100644 --- a/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py +++ b/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py @@ -8,20 +8,21 @@ import pytest from PySide6.QtCore import QCoreApplication -from PySide6.QtWidgets import QMessageBox +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QLabel +from PySide6.QtWidgets import QPushButton -from src.utils.error_message import ERROR_OPERATION_CANCELLED -from src.utils.info_message import INFO_SENDING_ERROR_REPORT from src.utils.local_store import local_store from src.version import __version__ from src.views.components.error_report_dialog_box import ErrorReportDialog +from src.views.components.toast import ToastManager @pytest.fixture def error_report_dialog(qtbot): """Fixture to create and return an instance of ErrorReportDialog.""" - url = 'http://example.com/error_report' - dialog = ErrorReportDialog(url) + dialog = ErrorReportDialog() + qtbot.addWidget(dialog) return dialog @@ -29,99 +30,78 @@ def test_dialog_initialization(error_report_dialog): """Test if the ErrorReportDialog initializes with correct properties.""" dialog = error_report_dialog + # Test window title expected_title = QCoreApplication.translate( 'iris_wallet_desktop', 'error_report', None, ) assert dialog.windowTitle() == expected_title - # Test the text in the dialog - assert 'something_went_wrong_mb' in dialog.text_sorry - assert 'error_description_mb' in dialog.text_help - assert 'what_will_be_included' in dialog.text_included - # Verify the dialog has 'Yes' and 'No' buttons - buttons = dialog.buttons() - assert len(buttons) == 2 - assert buttons[0].text().replace('&', '') == 'Yes' - assert buttons[1].text().replace('&', '') == 'No' + # Test if main components exist + assert isinstance(dialog.were_sorry_label, QLabel) + assert isinstance(dialog.help_us_label, QLabel) + assert isinstance(dialog.download_debug_logs, QPushButton) + assert isinstance(dialog.copy_button, QPushButton) + # Test labels content + assert QCoreApplication.translate( + 'iris_wallet_desktop', 'something_went_wrong_mb', None, + ) == dialog.were_sorry_label.text() -def test_send_report_on_yes_button(error_report_dialog): - """Test behavior when the 'Yes' button is clicked.""" + # Test button properties + assert dialog.download_debug_logs.minimumSize().width() == 120 + assert dialog.download_debug_logs.minimumSize().height() == 40 + + +def test_download_debug_logs(error_report_dialog, qtbot): + """Test the download debug logs functionality.""" dialog = error_report_dialog - # Mock external functions to prevent actual side effects during testing - with patch('src.views.components.error_report_dialog_box.ToastManager.info') as mock_info, \ + with patch('src.views.components.error_report_dialog_box.QFileDialog.getSaveFileName') as mock_file_dialog, \ patch('src.views.components.error_report_dialog_box.zip_logger_folder') as mock_zip, \ - patch('src.views.components.error_report_dialog_box.shutil.make_archive') as mock_archive, \ - patch('src.views.components.error_report_dialog_box.generate_error_report_email') as mock_generate_email, \ - patch('src.views.components.error_report_dialog_box.send_crash_report_async') as mock_send_email, \ - patch('src.views.components.error_report_dialog_box.report_email_server_config', {'email_id': 'dummy_email_id'}): # Mock email config dictionary + patch('src.views.components.error_report_dialog_box.download_file') as mock_download: - # Mock return values for external functions - mock_zip.return_value = ('dummy_dir', 'output_dir') - mock_archive.return_value = 'dummy_path.zip' - mock_generate_email.return_value = 'dummy email body' + # Mock return values + mock_zip.return_value = ('test.zip', 'output_dir') + mock_file_dialog.return_value = ('save_path.zip', 'selected_filter') - # Simulate a button click on "Yes" - dialog.buttonClicked.emit(dialog.button(QMessageBox.Yes)) + # Click download button + qtbot.mouseClick(dialog.download_debug_logs, Qt.LeftButton) - # Verify that the correct toast message is shown - mock_info.assert_called_once_with(INFO_SENDING_ERROR_REPORT) - - # Verify that the zip logger folder was called + # Verify zip_logger_folder was called with correct path mock_zip.assert_called_once_with(local_store.get_path()) - # Verify that make_archive was called to create the ZIP file - # Corrected the argument order to match the actual function call - mock_archive.assert_called_once_with('output_dir', 'zip', 'output_dir') - - # Verify the email generation - mock_generate_email.assert_called_once_with( - url='http://example.com/error_report', title='Error Report for Iris Wallet Desktop', - ) + # Verify download_file was called with correct parameters + mock_download.assert_called_once_with('save_path.zip', 'output_dir') - # Verify that the email sending function was called with correct parameters - mock_send_email.assert_called_once_with( - 'dummy_email_id', # The mocked email ID - f"Iris Wallet Error Report - Version {__version__}", - 'dummy email body', - 'dummy_path.zip', - ) - -def test_cancel_report_on_no_button(error_report_dialog): - """Test behavior when the 'No' button is clicked.""" +def test_download_debug_logs_cancelled(error_report_dialog, qtbot): + """Test when debug logs download is cancelled.""" dialog = error_report_dialog - # Mock ToastManager to avoid actual toast notifications - with patch('src.views.components.error_report_dialog_box.ToastManager.warning') as mock_warning: - # Simulate a button click on "No" - dialog.buttonClicked.emit(dialog.button(QMessageBox.No)) + with patch('src.views.components.error_report_dialog_box.QFileDialog.getSaveFileName') as mock_file_dialog, \ + patch('src.views.components.toast.ToastManager.show_toast') as mock_toast: - # Verify the correct warning toast message is shown - mock_warning.assert_called_once_with(ERROR_OPERATION_CANCELLED) + # Mock cancelled file dialog + mock_file_dialog.return_value = ('', '') + # Click download button + qtbot.mouseClick(dialog.download_debug_logs, Qt.LeftButton) -def test_dialog_buttons_functionality(error_report_dialog): - """Test if the 'Yes' and 'No' buttons work correctly.""" - dialog = error_report_dialog + # Verify toast was shown with correct message + mock_toast.assert_not_called() - # Mock ToastManager methods to prevent actual toasts from being shown - with patch('src.views.components.error_report_dialog_box.ToastManager.info') as mock_info, \ - patch('src.views.components.error_report_dialog_box.ToastManager.warning') as mock_warning: - # Check 'Yes' button functionality - yes_button = dialog.button(QMessageBox.Yes) - assert yes_button.text().replace('&', '') == 'Yes' - dialog.buttonClicked.emit(yes_button) +def test_copy_button(error_report_dialog, qtbot): + """Test if the copy button copies email to clipboard.""" + dialog = error_report_dialog - # Verify that the info toast was shown - mock_info.assert_called_once_with(INFO_SENDING_ERROR_REPORT) + with patch('src.views.components.error_report_dialog_box.copy_text') as mock_copy_text, \ + patch.object(ToastManager, 'success', return_value=None): + # Click copy button + qtbot.mouseClick(dialog.copy_button, Qt.LeftButton) - # Check 'No' button functionality - no_button = dialog.button(QMessageBox.No) - assert no_button.text().replace('&', '') == 'No' - dialog.buttonClicked.emit(no_button) + # Process events to allow click to propagate + qtbot.wait(100) - # Verify that the warning toast was shown - mock_warning.assert_called_once_with(ERROR_OPERATION_CANCELLED) + # Verify copy_text was called with correct label + mock_copy_text.assert_called_once_with(dialog.email_label) diff --git a/unit_tests/tests/utils_test/common_utils_test.py b/unit_tests/tests/utils_test/common_utils_test.py index d8ec881..e61194e 100644 --- a/unit_tests/tests/utils_test/common_utils_test.py +++ b/unit_tests/tests/utils_test/common_utils_test.py @@ -1,6 +1,6 @@ # Disable the redefined-outer-name warning as # it's normal to pass mocked object in tests function -# pylint: disable=redefined-outer-name,unused-argument, too-many-lines, no-value-for-parameter +# pylint: disable=redefined-outer-name,unused-argument, too-many-lines, no-value-for-parameter, use-implicit-booleaness-not-comparison """unit tests for common utils""" from __future__ import annotations @@ -26,14 +26,12 @@ from src.model.enums.enums_model import NetworkEnumModel from src.model.enums.enums_model import TokenSymbol from src.model.selection_page_model import SelectionPageModel -from src.utils.constant import DEFAULT_LOCALE from src.utils.common_utils import close_button_navigation from src.utils.common_utils import convert_hex_to_image from src.utils.common_utils import convert_timestamp from src.utils.common_utils import copy_text from src.utils.common_utils import download_file from src.utils.common_utils import find_files_with_name -from src.utils.common_utils import generate_error_report_email from src.utils.common_utils import generate_identicon from src.utils.common_utils import get_bitcoin_info_by_network from src.utils.common_utils import load_translator @@ -44,6 +42,7 @@ from src.utils.common_utils import translate_value from src.utils.common_utils import zip_logger_folder from src.utils.constant import APP_NAME +from src.utils.constant import DEFAULT_LOCALE from src.utils.constant import LOG_FOLDER_NAME from src.utils.custom_exception import CommonException from src.version import __version__ @@ -259,10 +258,6 @@ def test_convert_hex_to_image_valid_data(): pixmap = convert_hex_to_image(valid_hex) assert isinstance(pixmap, QPixmap) -# import time -# from unittest.mock import patch -# import os - def test_zip_logger_folder(): """ @@ -943,52 +938,6 @@ def test_resize_image_file_not_found(mock_exists): ) == 'The file /mock/path/nonexistent.png does not exist.' -def test_generate_error_report_email(mocker): - """Test generate_error_report_email function""" - # Mock all dependencies - mocker.patch( - 'src.data.repository.setting_repository.SettingRepository.get_wallet_network', - return_value=mocker.MagicMock(value='Mainnet'), - ) - mocker.patch('platform.system', return_value='Linux') - mocker.patch('platform.version', return_value='5.4.0-104-generic') - mocker.patch('platform.machine', return_value='x86_64') - mocker.patch( - 'platform.processor', - return_value='Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz', - ) - mocker.patch('src.version.__version__', '0.0.6') - - # Mock inputs - url = 'http://example.com/error' - title = 'Error Report' - - # Call the function - result = generate_error_report_email(url, title) - - # Assert the result matches the expected format - expected_system_info = ( - f"System Information Report:\n" - f"-------------------------\n" - f"URL: {url}\n" - f"Operating System: Linux\n" - f"OS Version: 5.4.0-104-generic\n" - f"Wallet Version: 0.0.6\n" - f"Wallet Network: Mainnet\n" - f"Architecture: x86_64\n" - f"Processor: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz\n" - ) - - expected_email_body = ( - f"{title}\n" - f"{'=' * len(title)}\n\n" - f"{expected_system_info}\n" - f"Attached logs can be found in the provided ZIP file for further details." - ) - - assert result == expected_email_body - - @patch('PySide6.QtWidgets.QMessageBox.warning') @patch('src.utils.ln_node_manage.LnNodeServerManager.get_instance') @patch('PySide6.QtWidgets.QApplication.instance') diff --git a/unit_tests/tests/utils_test/handle_exception_test.py b/unit_tests/tests/utils_test/handle_exception_test.py index 1d290ae..0a86238 100644 --- a/unit_tests/tests/utils_test/handle_exception_test.py +++ b/unit_tests/tests/utils_test/handle_exception_test.py @@ -49,13 +49,13 @@ def test_http_error_500_with_error_report(): with patch('src.utils.handle_exception.PageNavigationEventManager') as mock_manager: mock_instance = MagicMock() mock_manager.get_instance.return_value = mock_instance + mock_instance.error_report_signal = MagicMock() with pytest.raises(CommonException) as exc_info: handle_exceptions(exc) - mock_instance.error_report_signal.emit.assert_called_once_with( - 'http://test.url', - ) + # Verify error_report_signal.emit() was called with no arguments + mock_instance.error_report_signal.emit.assert_called_once_with() assert str(exc_info.value) == 'Server error' diff --git a/unit_tests/tests/utils_test/page_navigation_test.py b/unit_tests/tests/utils_test/page_navigation_test.py index 3799a8b..4389902 100644 --- a/unit_tests/tests/utils_test/page_navigation_test.py +++ b/unit_tests/tests/utils_test/page_navigation_test.py @@ -301,16 +301,15 @@ def test_sidebar(page_navigation, mock_ui): def test_error_report_dialog_box(page_navigation): """Test error_report_dialog_box method.""" - url = 'test_url' with patch('src.utils.page_navigation.ErrorReportDialog') as mock_dialog: mock_dialog_instance = MagicMock() mock_dialog.return_value = mock_dialog_instance # Call the method we're testing - page_navigation.error_report_dialog_box(url) + page_navigation.error_report_dialog_box() - # Verify the dialog was created with correct URL - mock_dialog.assert_called_once_with(url=url) + # Verify the dialog was created + mock_dialog.assert_called_once() # Verify the dialog was shown mock_dialog_instance.exec.assert_called_once()