Merge pull request #1045 from wger-project/feature/html-error-handling

Handle HTML errors in WgerHttpException
This commit is contained in:
Roland Geider
2025-12-17 19:21:53 +01:00
committed by GitHub
32 changed files with 284 additions and 139 deletions

View File

@@ -0,0 +1,85 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 2020 - 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart';
enum ErrorType {
json,
html,
text,
}
const HTML_ERROR_KEY = 'html_error';
class WgerHttpException implements Exception {
Map<String, dynamic> errors = {};
/// The exception type. While the majority will be json, it is possible that
/// the server will return HTML, e.g. if there has been an internal server error
/// or similar.
late ErrorType type;
/// Custom http exception
WgerHttpException(Response response) {
type = ErrorType.json;
final dynamic responseBody = response.body;
final contentType = response.headers[HttpHeaders.contentTypeHeader];
if (contentType != null && contentType.contains('text/html')) {
type = ErrorType.html;
}
if (responseBody == null) {
errors = {'unknown_error': 'An unknown error occurred, no further information available'};
} else {
try {
if (type == ErrorType.json) {
final response = json.decode(responseBody);
errors = (response is Map ? response : {'unknown_error': response})
.cast<String, dynamic>();
} else if (type == ErrorType.html) {
errors = {HTML_ERROR_KEY: responseBody.toString()};
} else {
errors = {'text_error': responseBody.toString()};
}
} catch (e) {
errors = {'unknown_error': responseBody};
}
}
}
WgerHttpException.fromMap(Map<String, dynamic> map) : type = ErrorType.json {
errors = map;
}
String get htmlError {
if (type != ErrorType.html) {
return '';
}
return errors[HTML_ERROR_KEY] ?? '';
}
@override
String toString() {
return 'WgerHttpException ($type): $errors';
}
}

View File

@@ -1,48 +0,0 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
class WgerHttpException implements Exception {
Map<String, dynamic> errors = {};
/// Custom http exception.
/// Expects the response body of the REST call and will try to parse it to
/// JSON. Will use the response as-is if it fails.
WgerHttpException(dynamic responseBody) {
if (responseBody == null) {
errors = {'unknown_error': 'An unknown error occurred, no further information available'};
} else {
try {
final response = json.decode(responseBody);
errors = (response is Map ? response : {'unknown_error': response}).cast<String, dynamic>();
} catch (e) {
errors = {'unknown_error': responseBody};
}
}
}
WgerHttpException.fromMap(Map<String, dynamic> map) {
errors = map;
}
@override
String toString() {
return errors.values.toList().join(', ');
}
}

View File

@@ -1,6 +1,6 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
* Copyright (c) 2020 - 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
@@ -22,11 +22,12 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/main.dart';
import 'package:wger/models/workouts/log.dart';
@@ -50,16 +51,23 @@ void showHttpExceptionErrorDialog(WgerHttpException exception, {BuildContext? co
return;
}
final errorList = formatApiErrors(extractErrors(exception.errors));
final theme = Theme.of(dialogContext);
showDialog(
context: dialogContext,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(ctx).anErrorOccurred),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [...errorList],
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (exception.type == ErrorType.html)
ServerHtmlError(data: exception.htmlError)
else
...formatApiErrors(extractErrors(exception.errors)),
],
),
),
actions: [
TextButton(
@@ -145,7 +153,10 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext
tilePadding: EdgeInsets.zero,
title: Text(i18n.errorViewDetails),
children: [
Text(issueErrorMessage, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(
issueErrorMessage,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.symmetric(vertical: 8.0),
@@ -237,6 +248,31 @@ void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext
);
}
/// A widget to render HTML errors returned by the server
///
/// This is a simple wrapper around the `Html` Widget, with some light changes
/// to the style.
class ServerHtmlError extends StatelessWidget {
final logger = Logger('ServerHtml');
final String data;
ServerHtmlError({required this.data, super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Html(
data: data,
style: {
'h1': Style(fontSize: FontSize(theme.textTheme.bodyLarge?.fontSize ?? 15)),
'h2': Style(fontSize: FontSize(theme.textTheme.bodyMedium?.fontSize ?? 15)),
},
doNotRenderTheseTags: const {'a'},
);
}
}
class CopyToClipboardButton extends StatelessWidget {
final logger = Logger('CopyToClipboardButton');
final String text;
@@ -423,38 +459,26 @@ class FormHttpErrorsWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
constraints: const BoxConstraints(maxHeight: 250),
decoration: BoxDecoration(
border: Border.all(color: theme.colorScheme.error, width: 1),
borderRadius: BorderRadius.circular(6),
),
padding: const EdgeInsets.all(10),
child: SingleChildScrollView(
child: Column(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
...formatApiErrors(
extractErrors(exception.errors),
color: Theme.of(context).colorScheme.error,
),
],
),
),
);
}
}
class GeneralErrorsWidget extends StatelessWidget {
final String? title;
final List<String> widgets;
const GeneralErrorsWidget(this.widgets, {this.title, super.key});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxHeight: 250),
child: SingleChildScrollView(
child: Column(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
...formatTextErrors(widgets, title: title, color: Theme.of(context).colorScheme.error),
Icon(Icons.error_outline, color: theme.colorScheme.error),
if (exception.type == ErrorType.html)
ServerHtmlError(data: exception.htmlError)
else
...formatApiErrors(
extractErrors(exception.errors),
color: theme.colorScheme.error,
),
],
),
),

View File

@@ -21,8 +21,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/core/locator.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/helpers/shared_preferences.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

View File

@@ -1,6 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/models/measurements/measurement_entry.dart';
part 'measurement_category.g.dart';

View File

@@ -1,6 +1,6 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
* Copyright (c) 2020 - 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
@@ -27,7 +27,7 @@ import 'package:http/http.dart' as http;
import 'package:logging/logging.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:version/version.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/shared_preferences.dart';
@@ -132,7 +132,7 @@ class AuthProvider with ChangeNotifier {
);
if (response.statusCode >= 400) {
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
return login(username, password, serverUrl, null);
@@ -158,8 +158,8 @@ class AuthProvider with ChangeNotifier {
},
);
if (response.statusCode != 200) {
throw WgerHttpException(response.body);
if (response.statusCode >= 400) {
throw WgerHttpException(response);
}
token = apiToken;
@@ -169,17 +169,18 @@ class AuthProvider with ChangeNotifier {
final response = await client.post(
makeUri(serverUrl, LOGIN_URL),
headers: {
HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
HttpHeaders.contentTypeHeader: 'application/json; charset=utf-8',
HttpHeaders.userAgentHeader: getAppNameHeader(),
},
body: json.encode({'username': username, 'password': password}),
);
final responseData = json.decode(response.body);
if (response.statusCode >= 400) {
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
final responseData = json.decode(response.body);
token = responseData['token'];
}

View File

@@ -21,7 +21,7 @@ import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http/http.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/providers/auth.dart';
import 'package:wger/providers/helpers.dart';
@@ -66,7 +66,7 @@ class WgerBaseProvider {
// Something wrong with our request
if (response.statusCode >= 400) {
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
// Process the response
@@ -104,7 +104,7 @@ class WgerBaseProvider {
// Something wrong with our request
if (response.statusCode >= 400) {
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
return json.decode(response.body);
@@ -120,7 +120,7 @@ class WgerBaseProvider {
// Something wrong with our request
if (response.statusCode >= 400) {
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
return json.decode(response.body);
@@ -137,7 +137,7 @@ class WgerBaseProvider {
// Something wrong with our request
if (response.statusCode >= 400) {
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
return response;
}

View File

@@ -18,7 +18,7 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/body_weight/weight_entry.dart';
import 'package:wger/providers/base_provider.dart';
@@ -115,7 +115,7 @@ class BodyWeightProvider with ChangeNotifier {
if (response.statusCode >= 400) {
_entries.insert(existingEntryIndex, existingWeightEntry);
notifyListeners();
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
}
}

View File

@@ -22,9 +22,9 @@ import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/core/locator.dart';
import 'package:wger/database/exercises/exercise_database.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/shared_preferences.dart';
import 'package:wger/models/exercises/category.dart';

View File

@@ -18,8 +18,8 @@
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/measurements/measurement_category.dart';
import 'package:wger/models/measurements/measurement_entry.dart';

View File

@@ -21,10 +21,10 @@ import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/core/locator.dart';
import 'package:wger/database/ingredients/ingredients_database.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/nutrition/ingredient.dart';
import 'package:wger/models/nutrition/ingredient_image.dart';
@@ -220,7 +220,7 @@ class NutritionPlansProvider with ChangeNotifier {
if (response.statusCode >= 400) {
_plans.insert(existingPlanIndex, existingPlan);
notifyListeners();
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
//existingPlan = null;
}
@@ -263,7 +263,7 @@ class NutritionPlansProvider with ChangeNotifier {
if (response.statusCode >= 400) {
plan.meals.insert(mealIndex, existingMeal);
notifyListeners();
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
}
@@ -293,7 +293,7 @@ class NutritionPlansProvider with ChangeNotifier {
if (response.statusCode >= 400) {
meal.mealItems.insert(mealItemIndex, existingMealItem);
notifyListeners();
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
}

View File

@@ -20,7 +20,7 @@ import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/shared_preferences.dart';
import 'package:wger/models/exercises/exercise.dart';
@@ -374,7 +374,7 @@ class RoutinesProvider with ChangeNotifier {
if (response.statusCode >= 400) {
_routines.insert(routineIndex, routine);
notifyListeners();
throw WgerHttpException(response.body);
throw WgerHttpException(response);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/core/wide_screen_wrapper.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

View File

@@ -19,7 +19,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

View File

@@ -1,13 +1,13 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
* 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,
* This program 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.
@@ -18,8 +18,8 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/core/wide_screen_wrapper.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/measurements/measurement_category.dart';
import 'package:wger/providers/measurement.dart';

View File

@@ -1,13 +1,13 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
* Copyright (c) 2020 - 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,
* This program 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.
@@ -20,6 +20,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:video_player/video_player.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/models/exercises/video.dart';
@@ -33,9 +34,10 @@ class ExerciseVideoWidget extends StatefulWidget {
}
class _ExerciseVideoWidgetState extends State<ExerciseVideoWidget> {
final logger = Logger('ExerciseVideoWidgetState');
late VideoPlayerController _controller;
bool hasError = false;
final logger = Logger('ExerciseVideoWidgetState');
@override
void initState() {
@@ -66,10 +68,14 @@ class _ExerciseVideoWidgetState extends State<ExerciseVideoWidget> {
@override
Widget build(BuildContext context) {
return hasError
? const GeneralErrorsWidget(
[
'An error happened while loading the video. If you can, please check the application logs.',
],
? FormHttpErrorsWidget(
WgerHttpException.fromMap(
const {
'error':
'An error happened while loading the video. If you can, '
'please check the application logs.',
},
),
)
: _controller.value.isInitialized
? AspectRatio(

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

View File

@@ -20,7 +20,7 @@ import 'package:clock/clock.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/helpers/json.dart';

View File

@@ -18,7 +18,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/workouts/day.dart';

View File

@@ -19,7 +19,7 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/l10n/generated/app_localizations.dart';

View File

@@ -21,7 +21,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart' as provider;
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/workouts/log.dart';

View File

@@ -0,0 +1,77 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (c) 2020 - 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:wger/core/exceptions/http_exception.dart';
void main() {
group('WgerHttpException', () {
test('parses valid JSON response', () {
final resp = http.Response(
'{"foo":"bar"}',
400,
headers: {HttpHeaders.contentTypeHeader: 'application/json'},
);
final ex = WgerHttpException(resp);
expect(ex.type, ErrorType.json);
expect(ex.errors['foo'], 'bar');
expect(ex.toString(), contains('WgerHttpException'));
});
test('falls back on malformed JSON', () {
const body = '{"foo":';
final resp = http.Response(
body,
500,
headers: {HttpHeaders.contentTypeHeader: 'application/json'},
);
final ex = WgerHttpException(resp);
expect(ex.type, ErrorType.json);
expect(ex.errors['unknown_error'], body);
});
test('detects HTML response', () {
const body = '<html lang="en"><body>Error</body></html>';
final resp = http.Response(
body,
500,
headers: {HttpHeaders.contentTypeHeader: 'text/html; charset=utf-8'},
);
final ex = WgerHttpException(resp);
expect(ex.type, ErrorType.html);
expect(ex.htmlError, body);
});
test('fromMap sets errors and type', () {
final map = <String, dynamic>{'field': 'value'};
final ex = WgerHttpException.fromMap(map);
expect(ex.type, ErrorType.json);
expect(ex.errors, map);
});
});
}

View File

@@ -21,7 +21,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/providers/add_exercise.dart';
import 'package:wger/providers/exercises.dart';
@@ -422,7 +422,7 @@ void main() {
testWidgets('Failed submission displays error message', (WidgetTester tester) async {
// Setup: Create verified user and mock failed submission
setupFullVerifiedUserContext();
final httpException = WgerHttpException({
final httpException = WgerHttpException.fromMap({
'name': ['This field is required'],
});
when(mockAddExerciseProvider.postExerciseToServer()).thenThrow(httpException);

View File

@@ -7,8 +7,8 @@ import 'package:mockito/mockito.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/database/exercises/exercise_database.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/shared_preferences.dart';
import 'package:wger/models/exercises/category.dart';

View File

@@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/models/measurements/measurement_category.dart';
import 'package:wger/models/measurements/measurement_entry.dart';

View File

@@ -4,8 +4,8 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/exceptions/no_such_entry_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/no_such_entry_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/measurements/measurement_category.dart';
import 'package:wger/models/measurements/measurement_entry.dart';
@@ -271,7 +271,7 @@ void main() {
'should re-add the "removed" MeasurementCategory and relay the exception on WgerHttpException',
() {
// arrange
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException('{}'));
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException.fromMap({}));
// act & assert
expect(
@@ -330,7 +330,7 @@ void main() {
test('should keep categories list as is on WgerHttpException', () {
// arrange
when(mockWgerBaseProvider.patch(any, any)).thenThrow(WgerHttpException('{}'));
when(mockWgerBaseProvider.patch(any, any)).thenThrow(WgerHttpException.fromMap({}));
// act & assert
expect(
@@ -550,7 +550,7 @@ void main() {
),
const MeasurementCategory(id: 2, name: 'Biceps', unit: 'cm'),
];
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException('{}'));
when(mockWgerBaseProvider.deleteRequest(any, any)).thenThrow(WgerHttpException.fromMap({}));
// act & assert
expect(

View File

@@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/workouts/session.dart';
import 'package:wger/providers/routines.dart';

View File

@@ -21,7 +21,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/core/exceptions/http_exception.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/workouts/routine.dart';