From 2879318c3c0dcaa2b4f77a34264894958964c222 Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Tue, 3 Mar 2026 17:39:57 -0500 Subject: [PATCH 1/4] adds a helper class to check sanity and implements widget to display sanity after auth --- lib/helpers/sanity_checks.dart | 93 +++++++++++++++++++ lib/providers/auth.dart | 23 +++++ lib/screens/auth_screen.dart | 12 +++ .../core/server_config_warning_dialog.dart | 50 ++++++++++ 4 files changed, 178 insertions(+) create mode 100644 lib/helpers/sanity_checks.dart create mode 100644 lib/widgets/core/server_config_warning_dialog.dart diff --git a/lib/helpers/sanity_checks.dart b/lib/helpers/sanity_checks.dart new file mode 100644 index 000000000..4762d68f7 --- /dev/null +++ b/lib/helpers/sanity_checks.dart @@ -0,0 +1,93 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (c) 2025 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +import 'dart:convert'; +import 'package:http/http.dart' as http; + +/// Checks if the server's reverse proxy is configured correctly +/// by comparing pagination URLs with the base URL domain. +Future checkServerPaginationUrls({ + required String baseUrl, + required String token, +}) async { + try { + final baseUri = Uri.parse(baseUrl); + final expectedHost = baseUri.host; + final expectedPort = baseUri.port; + final expectedScheme = baseUri.scheme; + + final response = await http.get( + Uri.parse('$baseUrl/api/v2/exercise/?limit=1'), + headers: { + 'Authorization': 'Token $token', + 'Accept': 'application/json', + }, + ); + + if (response.statusCode != 200) { + return const SanityCheckResult( + isValid: false, + message: 'Could not reach server endpoint', + ); + } + + final data = jsonDecode(response.body) as Map; + final nextUrl = data['next'] as String?; + + if (nextUrl == null) { + return const SanityCheckResult(isValid: true); + } + + final nextUri = Uri.parse(nextUrl); + + if (nextUri.host != expectedHost) { + return SanityCheckResult( + isValid: false, + message: + 'Server misconfiguration detected!\n\n' + 'You are connecting to: $expectedScheme://$expectedHost${expectedPort != 0 ? ":$expectedPort" : ""}\n' + 'But pagination links point to: ${nextUri.scheme}://${nextUri.host}${nextUri.hasPort ? ":${nextUri.port}" : ""}\n\n' + 'This usually means your reverse proxy is not configured correctly.', + detectedUrl: nextUrl, + ); + } + + if (nextUri.scheme != expectedScheme) { + return SanityCheckResult( + isValid: false, + message: + 'Protocol mismatch detected!\n\n' + 'You are using: $expectedScheme\n' + 'But server returns: ${nextUri.scheme}', + detectedUrl: nextUrl, + ); + } + + return const SanityCheckResult(isValid: true); + } catch (e) { + return SanityCheckResult( + isValid: false, + message: 'Error checking server configuration: $e', + ); + } +} + +/// Result of a server sanity check +class SanityCheckResult { + const SanityCheckResult({ + required this.isValid, + this.message, + this.detectedUrl, + }); + + final bool isValid; + final String? message; + final String? detectedUrl; +} diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 6bbcb044d..df016f8cf 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -29,6 +29,7 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:version/version.dart'; import 'package:wger/core/exceptions/http_exception.dart'; import 'package:wger/helpers/consts.dart'; +import 'package:wger/helpers/sanity_checks.dart'; import 'package:wger/helpers/shared_preferences.dart'; import 'helpers.dart'; @@ -53,6 +54,8 @@ class AuthProvider with ChangeNotifier { PackageInfo? applicationVersion; Map metadata = {}; AuthState state = AuthState.loggedOut; + String? _serverConfigWarning; + String? get serverConfigWarning => _serverConfigWarning; static const MIN_APP_VERSION_URL = 'min-app-version'; static const SERVER_VERSION_URL = 'version'; @@ -66,6 +69,12 @@ class AuthProvider with ChangeNotifier { this.client = client ?? http.Client(); } + /// Clear the server config warnings + void clearServerConfigWarning() { + _serverConfigWarning = null; + notifyListeners(); + } + /// flag to indicate that the application has successfully loaded all initial data bool dataInit = false; @@ -192,6 +201,20 @@ class AuthProvider with ChangeNotifier { return LoginActions.update; } + // Check server configuration sanity + try { + final sanityCheck = await checkServerPaginationUrls( + baseUrl: serverUrl, + token: token!, + ); + if (!sanityCheck.isValid && sanityCheck.message != null) { + _serverConfigWarning = sanityCheck.message; + notifyListeners(); + } + } catch (e) { + _logger.info('Sanity check error: $e'); + } + // Log user in state = AuthState.loggedIn; notifyListeners(); diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index aaa55e46a..77055adbb 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -30,6 +30,7 @@ import 'package:wger/widgets/auth/email_field.dart'; import 'package:wger/widgets/auth/password_field.dart'; import 'package:wger/widgets/auth/server_field.dart'; import 'package:wger/widgets/auth/username_field.dart'; +import 'package:wger/widgets/core/server_config_warning_dialog.dart'; import '../providers/auth.dart'; @@ -206,6 +207,17 @@ class _AuthCardState extends State { ); return; } + + if (context.mounted && res == LoginActions.proceed) { + final authProvider = context.read(); + if (authProvider.serverConfigWarning != null) { + if (context.mounted) { + showServerConfigWarning(context, authProvider.serverConfigWarning!); + authProvider.clearServerConfigWarning(); + } + } + } + if (context.mounted) { setState(() { _isLoading = false; diff --git a/lib/widgets/core/server_config_warning_dialog.dart b/lib/widgets/core/server_config_warning_dialog.dart new file mode 100644 index 000000000..9854bbacb --- /dev/null +++ b/lib/widgets/core/server_config_warning_dialog.dart @@ -0,0 +1,50 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (c) 2025 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +import 'package:flutter/material.dart'; + +/// Shows a warning dialog when server misconfiguration is detected +void showServerConfigWarning(BuildContext context, String message) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Row( + children: [ + Icon(Icons.warning, color: Colors.orange, size: 28), + SizedBox(width: 12), + Expanded(child: Text('Server Configuration Issue')), + ], + ), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(message), + const SizedBox(height: 16), + const Text( + 'The app may still work in some cases, but pagination and some features might be broken.', + style: TextStyle( + fontStyle: FontStyle.italic, + fontSize: 14, + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('I understand'), + ), + ], + ), + ); +} From 245d0b8112ec9f9ade5d35e1890157f2b7cfc639 Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Tue, 3 Mar 2026 18:12:11 -0500 Subject: [PATCH 2/4] adds i18n for the new widget --- lib/l10n/app_en.arb | 14 ++++++++++++- .../core/server_config_warning_dialog.dart | 20 +++++++++++-------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 8e94bea3d..728f2f454 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1136,5 +1136,17 @@ "addToOpenFoodFacts": "Add to Open Food Facts", "@addToOpenFoodFacts": { "description": "Label shown as the clickable link to go on Open Food Facts" - } + }, + "serverConfigIssueTitle": "Server Configuration Issue", + "@serverConfigIssueTitle": { + "description": "Title of the dialog shown when server misconfiguration is detected" + }, + "serverConfigIssueWarning": "The app may still work in some cases, but pagination and some features might be broken.", + "@serverConfigIssueWarning": { + "description": "Warning message explaining that the app might not work properly due to server misconfiguration" + }, + "understand": "I understand", + "@understand": { + "description": "Button label to acknowledge and dismiss the server configuration warning" + } } diff --git a/lib/widgets/core/server_config_warning_dialog.dart b/lib/widgets/core/server_config_warning_dialog.dart index 9854bbacb..429c99e48 100644 --- a/lib/widgets/core/server_config_warning_dialog.dart +++ b/lib/widgets/core/server_config_warning_dialog.dart @@ -9,17 +9,21 @@ */ import 'package:flutter/material.dart'; +import 'package:wger/helpers/misc.dart'; +import 'package:wger/l10n/generated/app_localizations.dart'; /// Shows a warning dialog when server misconfiguration is detected void showServerConfigWarning(BuildContext context, String message) { + final i18n = AppLocalizations.of(context); + showDialog( context: context, builder: (context) => AlertDialog( - title: const Row( + title: Row( children: [ - Icon(Icons.warning, color: Colors.orange, size: 28), - SizedBox(width: 12), - Expanded(child: Text('Server Configuration Issue')), + const Icon(Icons.warning, color: Colors.orange, size: 28), + const SizedBox(width: 12), + Expanded(child: Text(i18n.serverConfigIssueTitle)), ], ), content: SingleChildScrollView( @@ -29,9 +33,9 @@ void showServerConfigWarning(BuildContext context, String message) { children: [ Text(message), const SizedBox(height: 16), - const Text( - 'The app may still work in some cases, but pagination and some features might be broken.', - style: TextStyle( + Text( + i18n.serverConfigIssueWarning, + style: const TextStyle( fontStyle: FontStyle.italic, fontSize: 14, ), @@ -42,7 +46,7 @@ void showServerConfigWarning(BuildContext context, String message) { actions: [ TextButton( onPressed: () => Navigator.pop(context), - child: const Text('I understand'), + child: Text(i18n.understand), ), ], ), From e2dd9bfc7456309f40155e728d25993fda00f931 Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Tue, 3 Mar 2026 18:29:18 -0500 Subject: [PATCH 3/4] adds tests for the sanity check helper --- lib/helpers/sanity_checks.dart | 6 +- test/helpers/sanity_checks_test.dart | 225 +++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 test/helpers/sanity_checks_test.dart diff --git a/lib/helpers/sanity_checks.dart b/lib/helpers/sanity_checks.dart index 4762d68f7..ac8e0d708 100644 --- a/lib/helpers/sanity_checks.dart +++ b/lib/helpers/sanity_checks.dart @@ -16,14 +16,18 @@ import 'package:http/http.dart' as http; Future checkServerPaginationUrls({ required String baseUrl, required String token, + + /// Custom client is only used for tests + http.Client? client, }) async { + final httpClient = client ?? http.Client(); try { final baseUri = Uri.parse(baseUrl); final expectedHost = baseUri.host; final expectedPort = baseUri.port; final expectedScheme = baseUri.scheme; - final response = await http.get( + final response = await httpClient.get( Uri.parse('$baseUrl/api/v2/exercise/?limit=1'), headers: { 'Authorization': 'Token $token', diff --git a/test/helpers/sanity_checks_test.dart b/test/helpers/sanity_checks_test.dart new file mode 100644 index 000000000..ba10aacd0 --- /dev/null +++ b/test/helpers/sanity_checks_test.dart @@ -0,0 +1,225 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2025 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:http/testing.dart'; +import 'package:wger/helpers/sanity_checks.dart'; +import 'dart:convert'; + +void main() { + group('checkServerPaginationUrls', () { + const testToken = 'test-token-123'; + const testBaseUrl = 'https://wger.example.com'; + + test('returns valid when pagination URL matches base URL', () async { + final client = MockClient((request) async { + return http.Response( + jsonEncode({ + 'count': 100, + 'next': 'https://wger.example.com/api/v2/exercise/?limit=1&offset=1', + 'previous': null, + 'results': [], + }), + 200, + ); + }); + + final result = await checkServerPaginationUrls( + baseUrl: testBaseUrl, + token: testToken, + client: client, + ); + + expect(result.isValid, isTrue); + expect(result.message, isNull); + expect(result.detectedUrl, isNull); + }); + + test('detects host mismatch (localhost issue)', () async { + final client = MockClient((request) async { + return http.Response( + jsonEncode({ + 'count': 100, + 'next': 'http://localhost:8000/api/v2/exercise/?limit=1&offset=1', + 'previous': null, + 'results': [], + }), + 200, + ); + }); + + final result = await checkServerPaginationUrls( + baseUrl: testBaseUrl, + token: testToken, + client: client, + ); + + expect(result.isValid, isFalse); + expect(result.message, contains('Server misconfiguration detected')); + expect(result.message, contains('wger.example.com')); + expect(result.message, contains('localhost')); + expect(result.detectedUrl, 'http://localhost:8000/api/v2/exercise/?limit=1&offset=1'); + }); + + test('detects protocol mismatch (https vs http)', () async { + final client = MockClient((request) async { + return http.Response( + jsonEncode({ + 'count': 100, + 'next': 'http://wger.example.com/api/v2/exercise/?limit=1&offset=1', + 'previous': null, + 'results': [], + }), + 200, + ); + }); + + final result = await checkServerPaginationUrls( + baseUrl: testBaseUrl, + token: testToken, + client: client, + ); + + expect(result.isValid, isFalse); + expect(result.message, contains('Protocol mismatch detected')); + expect(result.message, contains('https')); + expect(result.message, contains('http')); + }); + + test('returns valid when next URL is null (no pagination)', () async { + final client = MockClient((request) async { + return http.Response( + jsonEncode({ + 'count': 1, + 'next': null, + 'previous': null, + 'results': [], + }), + 200, + ); + }); + + final result = await checkServerPaginationUrls( + baseUrl: testBaseUrl, + token: testToken, + client: client, + ); + + expect(result.isValid, isTrue); + }); + + test('handles server error gracefully', () async { + final client = MockClient((request) async { + return http.Response('Internal Server Error', 500); + }); + + final result = await checkServerPaginationUrls( + baseUrl: testBaseUrl, + token: testToken, + client: client, + ); + + expect(result.isValid, isFalse); + expect(result.message, contains('Could not reach server endpoint')); + }); + + test('handles network errors gracefully', () async { + final client = MockClient((request) async { + throw Exception('Network error'); + }); + + final result = await checkServerPaginationUrls( + baseUrl: testBaseUrl, + token: testToken, + client: client, + ); + + expect(result.isValid, isFalse); + expect(result.message, contains('Error checking server configuration')); + }); + + test('includes correct headers in request', () async { + String? authHeader; + String? acceptHeader; + + final client = MockClient((request) async { + authHeader = request.headers['Authorization']; + acceptHeader = request.headers['Accept']; + + return http.Response( + jsonEncode({'count': 0, 'next': null, 'results': []}), + 200, + ); + }); + + await checkServerPaginationUrls( + baseUrl: testBaseUrl, + token: testToken, + client: client, + ); + + expect(authHeader, 'Token $testToken'); + expect(acceptHeader, 'application/json'); + }); + + test('handles port numbers correctly in URL comparison', () async { + final client = MockClient((request) async { + return http.Response( + jsonEncode({ + 'count': 100, + 'next': 'https://wger.example.com:8443/api/v2/exercise/?limit=1&offset=1', + 'previous': null, + 'results': [], + }), + 200, + ); + }); + + final result = await checkServerPaginationUrls( + baseUrl: 'https://wger.example.com:8443', + token: testToken, + client: client, + ); + + expect(result.isValid, isTrue); + }); + }); + + group('SanityCheckResult', () { + test('creates valid result correctly', () { + const result = SanityCheckResult(isValid: true); + + expect(result.isValid, isTrue); + expect(result.message, isNull); + expect(result.detectedUrl, isNull); + }); + + test('creates invalid result with message', () { + const result = SanityCheckResult( + isValid: false, + message: 'Test error', + detectedUrl: 'http://localhost', + ); + + expect(result.isValid, isFalse); + expect(result.message, 'Test error'); + expect(result.detectedUrl, 'http://localhost'); + }); + }); +} From f4e2f1a6cbbde960f2e30b86d6806d20fa5942a8 Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Sat, 7 Mar 2026 17:34:27 -0500 Subject: [PATCH 4/4] shortens the displayed error message and adds link to doc --- lib/helpers/sanity_checks.dart | 41 +++---------------- lib/l10n/app_en.arb | 24 ++++++----- lib/providers/auth.dart | 11 ++--- lib/screens/auth_screen.dart | 4 +- .../core/server_config_warning_dialog.dart | 30 ++++++-------- test/helpers/sanity_checks_test.dart | 33 --------------- 6 files changed, 39 insertions(+), 104 deletions(-) diff --git a/lib/helpers/sanity_checks.dart b/lib/helpers/sanity_checks.dart index ac8e0d708..f3378aa9d 100644 --- a/lib/helpers/sanity_checks.dart +++ b/lib/helpers/sanity_checks.dart @@ -16,15 +16,13 @@ import 'package:http/http.dart' as http; Future checkServerPaginationUrls({ required String baseUrl, required String token, - - /// Custom client is only used for tests http.Client? client, }) async { final httpClient = client ?? http.Client(); + try { final baseUri = Uri.parse(baseUrl); final expectedHost = baseUri.host; - final expectedPort = baseUri.port; final expectedScheme = baseUri.scheme; final response = await httpClient.get( @@ -36,10 +34,7 @@ Future checkServerPaginationUrls({ ); if (response.statusCode != 200) { - return const SanityCheckResult( - isValid: false, - message: 'Could not reach server endpoint', - ); + return const SanityCheckResult(isValid: false); } final data = jsonDecode(response.body) as Map; @@ -51,35 +46,13 @@ Future checkServerPaginationUrls({ final nextUri = Uri.parse(nextUrl); - if (nextUri.host != expectedHost) { - return SanityCheckResult( - isValid: false, - message: - 'Server misconfiguration detected!\n\n' - 'You are connecting to: $expectedScheme://$expectedHost${expectedPort != 0 ? ":$expectedPort" : ""}\n' - 'But pagination links point to: ${nextUri.scheme}://${nextUri.host}${nextUri.hasPort ? ":${nextUri.port}" : ""}\n\n' - 'This usually means your reverse proxy is not configured correctly.', - detectedUrl: nextUrl, - ); - } - - if (nextUri.scheme != expectedScheme) { - return SanityCheckResult( - isValid: false, - message: - 'Protocol mismatch detected!\n\n' - 'You are using: $expectedScheme\n' - 'But server returns: ${nextUri.scheme}', - detectedUrl: nextUrl, - ); + if (nextUri.host != expectedHost || nextUri.scheme != expectedScheme) { + return const SanityCheckResult(isValid: false); } return const SanityCheckResult(isValid: true); } catch (e) { - return SanityCheckResult( - isValid: false, - message: 'Error checking server configuration: $e', - ); + return const SanityCheckResult(isValid: false); } } @@ -87,11 +60,7 @@ Future checkServerPaginationUrls({ class SanityCheckResult { const SanityCheckResult({ required this.isValid, - this.message, - this.detectedUrl, }); final bool isValid; - final String? message; - final String? detectedUrl; } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 728f2f454..4f21b2f4d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1139,14 +1139,18 @@ }, "serverConfigIssueTitle": "Server Configuration Issue", "@serverConfigIssueTitle": { - "description": "Title of the dialog shown when server misconfiguration is detected" - }, - "serverConfigIssueWarning": "The app may still work in some cases, but pagination and some features might be broken.", - "@serverConfigIssueWarning": { - "description": "Warning message explaining that the app might not work properly due to server misconfiguration" - }, - "understand": "I understand", - "@understand": { - "description": "Button label to acknowledge and dismiss the server configuration warning" - } + "description": "Title of the dialog shown when server misconfiguration is detected" + }, + "serverConfigIssueMessage": "Server misconfiguration detected, headers are not being passed correctly, please consult the documentation.", + "@serverConfigIssueMessage": { + "description": "Message explaining server misconfiguration and linking to documentation" + }, + "understand": "I understand", + "@understand": { + "description": "Button label to acknowledge and dismiss the server configuration warning" + }, + "viewDocumentation": "View documentation", + "@viewDocumentation": { + "description": "Button label to open the documentation about server configuration" + } } diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index df016f8cf..205fbb0ff 100644 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -54,8 +54,8 @@ class AuthProvider with ChangeNotifier { PackageInfo? applicationVersion; Map metadata = {}; AuthState state = AuthState.loggedOut; - String? _serverConfigWarning; - String? get serverConfigWarning => _serverConfigWarning; + bool _serverConfigWarning = false; + bool get serverConfigWarning => _serverConfigWarning; static const MIN_APP_VERSION_URL = 'min-app-version'; static const SERVER_VERSION_URL = 'version'; @@ -71,7 +71,7 @@ class AuthProvider with ChangeNotifier { /// Clear the server config warnings void clearServerConfigWarning() { - _serverConfigWarning = null; + _serverConfigWarning = false; notifyListeners(); } @@ -207,8 +207,9 @@ class AuthProvider with ChangeNotifier { baseUrl: serverUrl, token: token!, ); - if (!sanityCheck.isValid && sanityCheck.message != null) { - _serverConfigWarning = sanityCheck.message; + + if (!sanityCheck.isValid) { + _serverConfigWarning = true; notifyListeners(); } } catch (e) { diff --git a/lib/screens/auth_screen.dart b/lib/screens/auth_screen.dart index 77055adbb..f9d2e9328 100644 --- a/lib/screens/auth_screen.dart +++ b/lib/screens/auth_screen.dart @@ -210,9 +210,9 @@ class _AuthCardState extends State { if (context.mounted && res == LoginActions.proceed) { final authProvider = context.read(); - if (authProvider.serverConfigWarning != null) { + if (authProvider.serverConfigWarning) { if (context.mounted) { - showServerConfigWarning(context, authProvider.serverConfigWarning!); + showServerConfigWarning(context); authProvider.clearServerConfigWarning(); } } diff --git a/lib/widgets/core/server_config_warning_dialog.dart b/lib/widgets/core/server_config_warning_dialog.dart index 429c99e48..acf1e5feb 100644 --- a/lib/widgets/core/server_config_warning_dialog.dart +++ b/lib/widgets/core/server_config_warning_dialog.dart @@ -13,7 +13,7 @@ import 'package:wger/helpers/misc.dart'; import 'package:wger/l10n/generated/app_localizations.dart'; /// Shows a warning dialog when server misconfiguration is detected -void showServerConfigWarning(BuildContext context, String message) { +void showServerConfigWarning(BuildContext context) { final i18n = AppLocalizations.of(context); showDialog( @@ -26,28 +26,22 @@ void showServerConfigWarning(BuildContext context, String message) { Expanded(child: Text(i18n.serverConfigIssueTitle)), ], ), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(message), - const SizedBox(height: 16), - Text( - i18n.serverConfigIssueWarning, - style: const TextStyle( - fontStyle: FontStyle.italic, - fontSize: 14, - ), - ), - ], - ), - ), + content: Text(i18n.serverConfigIssueMessage), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text(i18n.understand), ), + TextButton( + onPressed: () { + launchURL( + 'https://wger.readthedocs.io/en/latest/administration/errors.html#wrong-pagination-links', + context, + ); + Navigator.pop(context); + }, + child: Text(i18n.viewDocumentation), + ), ], ), ); diff --git a/test/helpers/sanity_checks_test.dart b/test/helpers/sanity_checks_test.dart index ba10aacd0..b61083c98 100644 --- a/test/helpers/sanity_checks_test.dart +++ b/test/helpers/sanity_checks_test.dart @@ -47,8 +47,6 @@ void main() { ); expect(result.isValid, isTrue); - expect(result.message, isNull); - expect(result.detectedUrl, isNull); }); test('detects host mismatch (localhost issue)', () async { @@ -71,10 +69,6 @@ void main() { ); expect(result.isValid, isFalse); - expect(result.message, contains('Server misconfiguration detected')); - expect(result.message, contains('wger.example.com')); - expect(result.message, contains('localhost')); - expect(result.detectedUrl, 'http://localhost:8000/api/v2/exercise/?limit=1&offset=1'); }); test('detects protocol mismatch (https vs http)', () async { @@ -97,9 +91,6 @@ void main() { ); expect(result.isValid, isFalse); - expect(result.message, contains('Protocol mismatch detected')); - expect(result.message, contains('https')); - expect(result.message, contains('http')); }); test('returns valid when next URL is null (no pagination)', () async { @@ -136,7 +127,6 @@ void main() { ); expect(result.isValid, isFalse); - expect(result.message, contains('Could not reach server endpoint')); }); test('handles network errors gracefully', () async { @@ -151,7 +141,6 @@ void main() { ); expect(result.isValid, isFalse); - expect(result.message, contains('Error checking server configuration')); }); test('includes correct headers in request', () async { @@ -200,26 +189,4 @@ void main() { expect(result.isValid, isTrue); }); }); - - group('SanityCheckResult', () { - test('creates valid result correctly', () { - const result = SanityCheckResult(isValid: true); - - expect(result.isValid, isTrue); - expect(result.message, isNull); - expect(result.detectedUrl, isNull); - }); - - test('creates invalid result with message', () { - const result = SanityCheckResult( - isValid: false, - message: 'Test error', - detectedUrl: 'http://localhost', - ); - - expect(result.isValid, isFalse); - expect(result.message, 'Test error'); - expect(result.detectedUrl, 'http://localhost'); - }); - }); }