Bump version from 1.0.12 to 1.0.13

- Updated version strings in main.py, build.py, mbetterclient/__init__.py, mbetterclient/web_dashboard/app.py
- Updated user agent from MbetterClient/1.0r12 to MbetterClient/1.0r13 in mbetterclient/config/settings.py
- Updated user agent in test_reports_sync_fix.py
- Added changelog entry for version 1.0.13
parent bf87924a
......@@ -2,6 +2,12 @@
All notable changes to this project will be documented in this file.
## [1.0.13] - 2026-02-02
### Changed
- Version bump from 1.0.12 to 1.0.13
- Updated user agent string from MbetterClient/1.0r12 to MbetterClient/1.0r13
## [1.0.12] - 2026-01-26
### Added
......
......@@ -14,7 +14,7 @@ from typing import List, Dict, Any
# Build configuration
BUILD_CONFIG = {
'app_name': 'MbetterClient',
'app_version': '1.0.12',
'app_version': '1.0.13',
'description': 'Cross-platform multimedia client application',
'author': 'MBetter Team',
'entry_point': 'main.py',
......
......@@ -211,7 +211,7 @@ Examples:
parser.add_argument(
'--version',
action='version',
version='MbetterClient 1.0.12'
version='MbetterClient 1.0.13'
)
# Timer options
......
......@@ -4,7 +4,7 @@ MbetterClient - Cross-platform multimedia client application
A multi-threaded application with video playback, web dashboard, and REST API integration.
"""
__version__ = "1.0.12"
__version__ = "1.0.13"
__author__ = "MBetter Project"
__email__ = "dev@mbetter.net"
__description__ = "Cross-platform multimedia client with video overlay and web dashboard"
......
......@@ -262,7 +262,7 @@ class ApiConfig:
# Request settings
verify_ssl: bool = True
user_agent: str = "MbetterClient/1.0r12"
user_agent: str = "MbetterClient/1.0r13"
max_response_size_mb: int = 100
# Additional API client settings
......@@ -366,7 +366,7 @@ class AppSettings:
timer: TimerConfig = field(default_factory=TimerConfig)
# Application settings
version: str = "1.0.12"
version: str = "1.0.13"
debug_mode: bool = False
dev_message: bool = False # Enable debug mode showing only message bus messages
debug_messages: bool = False # Show all messages passing through the message bus on screen
......
......@@ -209,7 +209,7 @@ class WebDashboard(ThreadedComponent):
def inject_globals():
return {
'app_name': 'MbetterClient',
'app_version': '1.0.12',
'app_version': '1.0.13',
'current_time': time.time(),
}
......
"""
Test script to verify reports sync fix
Tests that the reports_sync endpoint now sends actual report data instead of empty JSON
"""
import sys
import os
import json
from unittest.mock import Mock, MagicMock, patch, call
from datetime import datetime
# Add project root to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from mbetterclient.api_client.client import APIClient, ReportsSyncResponseHandler, APIEndpoint
from mbetterclient.database.manager import DatabaseManager
from mbetterclient.config.manager import ConfigManager
from mbetterclient.config.settings import ApiConfig
from mbetterclient.core.message_bus import MessageBus, MessageType
def test_reports_sync_sends_data():
"""Test that reports_sync endpoint sends actual report data"""
print("\n=== Test: Reports Sync Sends Actual Data ===")
# Create mock dependencies
mock_db_manager = Mock()
mock_config_manager = Mock()
mock_settings = Mock(spec=ApiConfig)
mock_settings.rustdesk_id = "test_rustdesk_123"
mock_settings.user_agent = "MbetterClient/1.0r13"
mock_settings.verify_ssl = False
mock_settings.timeout_seconds = 30
mock_settings.retry_attempts = 3
mock_settings.retry_delay_seconds = 5
mock_settings.max_consecutive_failures = 5
# Mock config manager to return API config
mock_config_manager.get_section_config = Mock(side_effect=lambda section: {
"api": {
"fastapi_url": "https://mbetter.nexlab.net",
"api_token": "test_token_12345",
"api_interval": 1800
}
}.get(section, {}))
# Mock message bus
mock_message_bus = Mock(spec=MessageBus)
mock_message_bus.register_component = Mock(return_value=Mock())
mock_message_bus.subscribe = Mock()
mock_message_bus.publish = Mock()
mock_message_bus.get_message = Mock(return_value=None)
# Create API client
with patch('mbetterclient.api_client.client.create_requests_session_with_ssl_support') as mock_session:
mock_session.return_value = Mock()
api_client = APIClient(
message_bus=mock_message_bus,
db_manager=mock_db_manager,
config_manager=mock_config_manager,
settings=mock_settings
)
# Initialize the client
api_client.initialize()
# Get reports_sync endpoint
reports_sync_endpoint = api_client.endpoints.get('reports_sync')
assert reports_sync_endpoint is not None, "reports_sync endpoint should exist"
print(f"✓ reports_sync endpoint found")
print(f"✓ Endpoint URL: {reports_sync_endpoint.url}")
print(f"✓ Endpoint method: {reports_sync_endpoint.method}")
# Mock the reports handler's collect_report_data method
reports_handler = api_client.response_handlers.get('reports_sync')
assert reports_handler is not None, "reports_sync handler should exist"
# Create mock report data
mock_report_data = {
'sync_id': 'test_sync_001',
'client_id': 'test_client_123',
'sync_timestamp': datetime.utcnow().isoformat(),
'date_range': 'today',
'start_date': '2026-02-01T00:00:00',
'end_date': '2026-02-01T23:59:59',
'bets': [
{
'uuid': 'bet_001',
'fixture_id': 'fixture_001',
'bet_datetime': '2026-02-01T10:00:00',
'paid': True,
'paid_out': False,
'total_amount': 100.0,
'bet_count': 2,
'details': []
}
],
'extraction_stats': [
{
'match_id': 1,
'fixture_id': 'fixture_001',
'match_datetime': '2026-02-01T10:00:00',
'total_bets': 10,
'total_amount_collected': 1000.0,
'total_redistributed': 950.0,
'actual_result': 'WIN1',
'extraction_result': 'WIN1',
'cap_applied': False,
'cap_percentage': None,
'under_bets': 5,
'under_amount': 500.0,
'over_bets': 5,
'over_amount': 500.0,
'result_breakdown': {}
}
],
'summary': {
'total_payin': 100.0,
'total_payout': 950.0,
'net_profit': -850.0,
'total_bets': 2,
'total_matches': 1
}
}
# Patch collect_report_data to return mock data
with patch.object(reports_handler, 'collect_report_data', return_value=mock_report_data):
# Mock the session.request method to capture the request data
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'success': True,
'synced_count': 1,
'message': 'Sync successful'
}
mock_response.headers = {'content-type': 'application/json'}
mock_response.text = json.dumps({'success': True, 'synced_count': 1})
api_client.session.request = Mock(return_value=mock_response)
# Execute the endpoint request
api_client._execute_endpoint_request(reports_sync_endpoint)
# Verify that session.request was called
assert api_client.session.request.called, "session.request should have been called"
# Get the call arguments
call_args = api_client.session.request.call_args
# Verify the request was made with correct parameters
assert call_args is not None, "session.request should have been called with arguments"
# Check the JSON data sent
json_data = call_args.kwargs.get('json', {})
print(f"\n✓ Request was made")
print(f"✓ Request method: {call_args.kwargs.get('method')}")
print(f"✓ Request URL: {call_args.kwargs.get('url')}")
# Verify that the JSON data is not empty
assert json_data is not None, "JSON data should not be None"
assert isinstance(json_data, dict), "JSON data should be a dictionary"
assert len(json_data) > 0, "JSON data should not be empty"
print(f"✓ JSON data is not empty")
print(f"✓ JSON data keys: {list(json_data.keys())}")
# Verify that the JSON data contains the expected fields
assert 'sync_id' in json_data, "JSON data should contain 'sync_id'"
assert 'client_id' in json_data, "JSON data should contain 'client_id'"
assert 'bets' in json_data, "JSON data should contain 'bets'"
assert 'extraction_stats' in json_data, "JSON data should contain 'extraction_stats'"
print(f"✓ JSON data contains expected fields")
print(f"✓ sync_id: {json_data.get('sync_id')}")
print(f"✓ client_id: {json_data.get('client_id')}")
print(f"✓ Number of bets: {len(json_data.get('bets', []))}")
print(f"✓ Number of extraction stats: {len(json_data.get('extraction_stats', []))}")
# Verify that the data matches the mock data
assert json_data['sync_id'] == mock_report_data['sync_id'], "sync_id should match"
assert json_data['client_id'] == mock_report_data['client_id'], "client_id should match"
assert len(json_data['bets']) == len(mock_report_data['bets']), "Number of bets should match"
assert len(json_data['extraction_stats']) == len(mock_report_data['extraction_stats']), "Number of extraction stats should match"
print(f"✓ JSON data matches mock report data")
# Verify that collect_report_data was called
reports_handler.collect_report_data.assert_called_once_with(date_range='today')
print(f"✓ collect_report_data was called with date_range='today'")
print("\n✅ TEST PASSED: Reports sync now sends actual report data instead of empty JSON")
return True
def test_reports_sync_empty_data_fallback():
"""Test that reports_sync handles data collection failure gracefully"""
print("\n=== Test: Reports Sync Handles Data Collection Failure ===")
# Create mock dependencies
mock_db_manager = Mock()
mock_config_manager = Mock()
mock_settings = Mock(spec=ApiConfig)
mock_settings.rustdesk_id = "test_rustdesk_123"
mock_settings.user_agent = "MbetterClient/1.0r13"
mock_settings.verify_ssl = False
mock_settings.timeout_seconds = 30
mock_settings.retry_attempts = 3
mock_settings.retry_delay_seconds = 5
mock_settings.max_consecutive_failures = 5
# Mock config manager to return API config
mock_config_manager.get_section_config = Mock(side_effect=lambda section: {
"api": {
"fastapi_url": "https://mbetter.nexlab.net",
"api_token": "test_token_12345",
"api_interval": 1800
}
}.get(section, {}))
# Mock message bus
mock_message_bus = Mock(spec=MessageBus)
mock_message_bus.register_component = Mock(return_value=Mock())
mock_message_bus.subscribe = Mock()
mock_message_bus.publish = Mock()
mock_message_bus.get_message = Mock(return_value=None)
# Create API client
with patch('mbetterclient.api_client.client.create_requests_session_with_ssl_support') as mock_session:
mock_session.return_value = Mock()
api_client = APIClient(
message_bus=mock_message_bus,
db_manager=mock_db_manager,
config_manager=mock_config_manager,
settings=mock_settings
)
# Initialize the client
api_client.initialize()
# Get reports_sync endpoint
reports_sync_endpoint = api_client.endpoints.get('reports_sync')
# Get reports handler
reports_handler = api_client.response_handlers.get('reports_sync')
# Patch collect_report_data to raise an exception
with patch.object(reports_handler, 'collect_report_data', side_effect=Exception("Database error")):
# Mock the session.request method
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {
'success': True,
'synced_count': 0,
'message': 'Sync successful'
}
mock_response.headers = {'content-type': 'application/json'}
mock_response.text = json.dumps({'success': True, 'synced_count': 0})
api_client.session.request = Mock(return_value=mock_response)
# Execute the endpoint request
api_client._execute_endpoint_request(reports_sync_endpoint)
# Verify that session.request was called
assert api_client.session.request.called, "session.request should have been called"
# Get the call arguments
call_args = api_client.session.request.call_args
# Check the JSON data sent
json_data = call_args.kwargs.get('json', {})
print(f"✓ Request was made even after data collection failure")
print(f"✓ JSON data: {json_data}")
# Verify that empty data was sent as fallback
assert json_data == {}, "Empty JSON should be sent as fallback"
print(f"✓ Empty JSON sent as fallback when data collection fails")
print("\n✅ TEST PASSED: Reports sync handles data collection failure gracefully")
return True
def run_all_tests():
"""Run all tests"""
print("=" * 80)
print("REPORTS SYNC FIX VERIFICATION TEST SUITE")
print("=" * 80)
tests = [
test_reports_sync_sends_data,
test_reports_sync_empty_data_fallback
]
passed = 0
failed = 0
for test_func in tests:
try:
test_func()
passed += 1
except AssertionError as e:
print(f"\n✗ Test failed: {e}")
failed += 1
except Exception as e:
print(f"\n✗ Test error: {e}")
import traceback
traceback.print_exc()
failed += 1
print("\n" + "=" * 80)
print("TEST SUMMARY")
print("=" * 80)
print(f"Total tests: {len(tests)}")
print(f"Passed: {passed}")
print(f"Failed: {failed}")
print(f"Success rate: {(passed/len(tests)*100):.1f}%")
print("=" * 80)
return failed == 0
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment