feat: Add tests, update notes implementation

This commit is contained in:
Anton Stubenbord
2023-12-31 15:26:20 +01:00
parent d7f297a4df
commit 55aa42e4ab
29 changed files with 273 additions and 115 deletions

View File

@@ -9,4 +9,9 @@ class InfoMessageException implements Exception {
this.message, this.message,
this.stackTrace, this.stackTrace,
}); });
@override
String toString() {
return 'InfoMessageException(code: $code, message: $message, stackTrace: $stackTrace)';
}
} }

View File

@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart'; import 'package:paperless_api/src/interceptor/dio_http_error_interceptor.dart';
import 'package:paperless_mobile/core/interceptor/dio_offline_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/dio_offline_interceptor.dart';
import 'package:paperless_mobile/core/interceptor/dio_unauthorized_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/dio_unauthorized_interceptor.dart';
import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart';

View File

@@ -82,5 +82,7 @@ String translateError(BuildContext context, ErrorCode code) {
'Could not load custom field.', //TODO: INTL 'Could not load custom field.', //TODO: INTL
ErrorCode.customFieldDeleteFailed => ErrorCode.customFieldDeleteFailed =>
'Could not delete custom field, please try again.', //TODO: INTL 'Could not delete custom field, please try again.', //TODO: INTL
ErrorCode.deleteNoteFailed => 'Could not delete note, please try again.',
ErrorCode.addNoteFailed => 'Could not create note, please try again.',
}; };
} }

View File

@@ -311,4 +311,17 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
_notifier.removeListener(this); _notifier.removeListener(this);
await super.close(); await super.close();
} }
Future<void> addNote(String text) async {
assert(state.status == LoadingStatus.loaded);
try {
final updatedDocument = await _api.addNote(
document: state.document!,
text: text,
);
_notifier.notifyUpdated(updatedDocument);
} on PaperlessApiException catch (err) {
addError(TransientPaperlessApiError(code: err.code));
}
}
} }

View File

@@ -1,37 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class AddNotePage extends StatefulWidget {
final DocumentModel document;
const AddNotePage({super.key, required this.document});
@override
State<AddNotePage> createState() => _AddNotePageState();
}
class _AddNotePageState extends State<AddNotePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(S.of(context)!.addNote),
),
body: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: S.of(context)!.content,
),
),
ElevatedButton(
onPressed: () {},
child: Text(S.of(context)!.save),
),
],
),
);
}
}

View File

@@ -8,18 +8,65 @@ import 'package:paperless_mobile/features/document_details/cubit/document_detail
import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
class DocumentNotesWidget extends StatelessWidget { class DocumentNotesWidget extends StatefulWidget {
final DocumentModel document; final DocumentModel document;
const DocumentNotesWidget({super.key, required this.document}); const DocumentNotesWidget({super.key, required this.document});
@override
State<DocumentNotesWidget> createState() => _DocumentNotesWidgetState();
}
class _DocumentNotesWidgetState extends State<DocumentNotesWidget> {
final _noteContentController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SliverMainAxisGroup( return SliverMainAxisGroup(
slivers: [ slivers: [
SliverToBoxAdapter(
child: Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _noteContentController,
maxLines: null,
validator: (value) {
if (value?.isEmpty ?? true) {
return S.of(context)!.thisFieldIsRequired;
}
return null;
},
decoration: InputDecoration(
hintText: 'Your note here...',
labelText: 'New note',
floatingLabelBehavior: FloatingLabelBehavior.always,
),
).padded(),
Align(
alignment: Alignment.centerRight,
child: FilledButton.icon(
icon: Icon(Icons.note_add_outlined),
label: Text("Add note"),
onPressed: () {
_formKey.currentState?.save();
if (_formKey.currentState?.validate() ?? false) {
context
.read<DocumentDetailsCubit>()
.addNote(_noteContentController.text);
}
},
).padded(),
),
],
).padded(),
),
),
SliverList.separated( SliverList.separated(
separatorBuilder: (context, index) => const SizedBox(height: 16), separatorBuilder: (context, index) => const SizedBox(height: 16),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final note = document.notes.elementAt(index); final note = widget.document.notes.elementAt(index);
return Card( return Card(
// borderRadius: BorderRadius.circular(8), // borderRadius: BorderRadius.circular(8),
// elevation: 1, // elevation: 1,
@@ -51,13 +98,6 @@ class DocumentNotesWidget extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Spacer(),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
// Push edit page
},
),
IconButton( IconButton(
icon: Icon(Icons.delete), icon: Icon(Icons.delete),
onPressed: () { onPressed: () {
@@ -74,7 +114,7 @@ class DocumentNotesWidget extends StatelessWidget {
).padded(16), ).padded(16),
); );
}, },
itemCount: document.notes.length, itemCount: widget.document.notes.length,
), ),
], ],
); );

View File

@@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
class EditNotePage extends StatefulWidget {
const EditNotePage({super.key});
@override
State<EditNotePage> createState() => _EditNotePageState();
}
class _EditNotePageState extends State<EditNotePage> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@@ -54,7 +54,9 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
Future<void> removeScan(File file) async { Future<void> removeScan(File file) async {
try { try {
if (await file.exists()) {
await file.delete(); await file.delete();
}
} catch (error, stackTrace) { } catch (error, stackTrace) {
throw InfoMessageException( throw InfoMessageException(
code: ErrorCode.scanRemoveFailed, code: ErrorCode.scanRemoveFailed,

View File

@@ -14,6 +14,7 @@ import 'package:paperless_mobile/core/bloc/loading_status.dart';
import 'package:paperless_mobile/core/database/hive/hive_config.dart'; import 'package:paperless_mobile/core/database/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/global/constants.dart'; import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/model/info_message_exception.dart';
import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart';
import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart'; import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart';
@@ -326,6 +327,8 @@ class _ScannerPageState extends State<ScannerPage>
.removeScan(scans[index]); .removeScan(scans[index]);
} on PaperlessApiException catch (error, stackTrace) { } on PaperlessApiException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} on InfoMessageException catch (error, stackTrace) {
showInfoMessage(context, error, stackTrace);
} }
}, },
index: index, index: index,

View File

@@ -9,7 +9,6 @@ import 'package:paperless_mobile/features/document_bulk_action/view/widgets/full
import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart'; import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/add_note_page.dart';
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart'; import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart';
import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart'; import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
@@ -211,16 +210,3 @@ class BulkEditDocumentsRoute extends GoRouteData {
); );
} }
} }
class AddNoteRoute extends GoRouteData {
final DocumentModel $extra;
AddNoteRoute({required this.$extra});
@override
Widget build(BuildContext context, GoRouterState state) {
return AddNotePage(
document: $extra,
);
}
}

View File

@@ -85,10 +85,6 @@ part 'authenticated_route.g.dart';
path: 'preview', path: 'preview',
name: R.documentPreview, name: R.documentPreview,
), ),
TypedGoRoute<AddNoteRoute>(
path: 'add-note',
name: R.addNote,
),
], ],
) )
], ],

View File

@@ -4,3 +4,4 @@ export 'src/models/models.dart';
export 'src/modules/modules.dart'; export 'src/modules/modules.dart';
export 'src/converters/converters.dart'; export 'src/converters/converters.dart';
export 'config/hive/hive_type_ids.dart'; export 'config/hive/hive_type_ids.dart';
export 'src/interceptor/dio_http_error_interceptor.dart';

View File

@@ -125,8 +125,8 @@ class DocumentFilter extends Equatable {
return queryParams; return queryParams;
} }
@override // @override
String toString() => toQueryParameters().toString(); // String toString() => toQueryParameters().toString();
DocumentFilter copyWith({ DocumentFilter copyWith({
int? pageSize, int? pageSize,
@@ -249,9 +249,4 @@ class DocumentFilter extends Equatable {
moreLike, moreLike,
selectedView, selectedView,
]; ];
// factory DocumentFilter.fromJson(Map<String, dynamic> json) =>
// _$DocumentFilterFromJson(json);
// Map<String, dynamic> toJson() => _$DocumentFilterToJson(this);
} }

View File

@@ -82,7 +82,6 @@ class FilterRule with EquatableMixin {
assert(filter.tags is IdsTagsQuery); assert(filter.tags is IdsTagsQuery);
return filter.copyWith( return filter.copyWith(
tags: switch (filter.tags) { tags: switch (filter.tags) {
// TODO: Handle this case.
IdsTagsQuery(include: var i, exclude: var e) => IdsTagsQuery( IdsTagsQuery(include: var i, exclude: var e) => IdsTagsQuery(
include: [...i, int.parse(value!)], include: [...i, int.parse(value!)],
exclude: e, exclude: e,

View File

@@ -28,4 +28,4 @@ export 'task/task.dart';
export 'task/task_status.dart'; export 'task/task_status.dart';
export 'user_model.dart'; export 'user_model.dart';
export 'exception/exceptions.dart'; export 'exception/exceptions.dart';
export 'note_model.dart'; export 'note_model.dart' show NoteModel;

View File

@@ -1,3 +1,5 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
part 'note_model.freezed.dart'; part 'note_model.freezed.dart';
part 'note_model.g.dart'; part 'note_model.g.dart';
@@ -9,9 +11,19 @@ class NoteModel with _$NoteModel {
required String? note, required String? note,
required DateTime? created, required DateTime? created,
required int? document, required int? document,
required int? user, @JsonKey(fromJson: parseNoteUserFromJson) required int? user,
}) = _NoteModel; }) = _NoteModel;
factory NoteModel.fromJson(Map<String, dynamic> json) => factory NoteModel.fromJson(Map<String, dynamic> json) =>
_$NoteModelFromJson(json); _$NoteModelFromJson(json);
} }
int? parseNoteUserFromJson(dynamic json) {
if (json == null) return null;
if (json is Map) {
return json['id'];
} else if (json is int) {
return json;
}
return null;
}

View File

@@ -11,7 +11,16 @@ class PaperlessApiException implements Exception {
this.httpStatusCode, this.httpStatusCode,
}); });
const PaperlessApiException.unknown() : this(ErrorCode.unknown); const PaperlessApiException.unknown({
String? details,
StackTrace? stackTrace,
int? httpStatusCode,
}) : this(
ErrorCode.unknown,
details: details,
stackTrace: stackTrace,
httpStatusCode: httpStatusCode,
);
@override @override
String toString() { String toString() {
@@ -71,5 +80,7 @@ enum ErrorCode {
updateSavedViewError, updateSavedViewError,
customFieldCreateFailed, customFieldCreateFailed,
customFieldLoadFailed, customFieldLoadFailed,
customFieldDeleteFailed; customFieldDeleteFailed,
deleteNoteFailed,
addNoteFailed;
} }

View File

@@ -11,7 +11,7 @@ import 'date_range_unit.dart';
part 'date_range_query.g.dart'; part 'date_range_query.g.dart';
sealed class DateRangeQuery { sealed class DateRangeQuery with EquatableMixin {
const DateRangeQuery(); const DateRangeQuery();
Map<String, String> toQueryParameter(DateRangeQueryField field); Map<String, String> toQueryParameter(DateRangeQueryField field);
@@ -28,10 +28,13 @@ class UnsetDateRangeQuery extends DateRangeQuery {
@override @override
bool matches(DateTime dt) => true; bool matches(DateTime dt) => true;
@override
List<Object?> get props => [];
} }
@HiveType(typeId: PaperlessApiHiveTypeIds.relativeDateRangeQuery) @HiveType(typeId: PaperlessApiHiveTypeIds.relativeDateRangeQuery)
class RelativeDateRangeQuery extends DateRangeQuery with EquatableMixin { class RelativeDateRangeQuery extends DateRangeQuery {
@HiveField(0) @HiveField(0)
final int offset; final int offset;
@HiveField(1) @HiveField(1)
@@ -84,7 +87,7 @@ class RelativeDateRangeQuery extends DateRangeQuery with EquatableMixin {
@JsonSerializable() @JsonSerializable()
@HiveType(typeId: PaperlessApiHiveTypeIds.absoluteDateRangeQuery) @HiveType(typeId: PaperlessApiHiveTypeIds.absoluteDateRangeQuery)
class AbsoluteDateRangeQuery extends DateRangeQuery with EquatableMixin { class AbsoluteDateRangeQuery extends DateRangeQuery {
@LocalDateTimeJsonConverter() @LocalDateTimeJsonConverter()
@HiveField(0) @HiveField(0)
final DateTime? after; final DateTime? after;

View File

@@ -4,7 +4,7 @@ import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'id_query_parameter.g.dart'; part 'id_query_parameter.g.dart';
sealed class IdQueryParameter { sealed class IdQueryParameter with EquatableMixin {
const IdQueryParameter(); const IdQueryParameter();
Map<String, String> toQueryParameter(String field); Map<String, String> toQueryParameter(String field);
bool matches(int? id); bool matches(int? id);
@@ -23,6 +23,9 @@ class UnsetIdQueryParameter extends IdQueryParameter {
@override @override
bool matches(int? id) => true; bool matches(int? id) => true;
@override
List<Object?> get props => [];
} }
// @HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedIdQueryParameter) // @HiveType(typeId: PaperlessApiHiveTypeIds.notAssignedIdQueryParameter)
@@ -36,6 +39,8 @@ class NotAssignedIdQueryParameter extends IdQueryParameter {
@override @override
bool matches(int? id) => id == null; bool matches(int? id) => id == null;
@override
List<Object?> get props => [];
} }
// @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedIdQueryParameter) // @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedIdQueryParameter)
@@ -48,6 +53,8 @@ class AnyAssignedIdQueryParameter extends IdQueryParameter {
@override @override
bool matches(int? id) => id != null; bool matches(int? id) => id != null;
@override
List<Object?> get props => [];
} }
@HiveType(typeId: PaperlessApiHiveTypeIds.setIdQueryParameter) @HiveType(typeId: PaperlessApiHiveTypeIds.setIdQueryParameter)

View File

@@ -4,7 +4,7 @@ import 'package:paperless_api/config/hive/hive_type_ids.dart';
part 'tags_query.g.dart'; part 'tags_query.g.dart';
sealed class TagsQuery { sealed class TagsQuery with EquatableMixin {
const TagsQuery(); const TagsQuery();
Map<String, String> toQueryParameter(); Map<String, String> toQueryParameter();
bool matches(Iterable<int> ids); bool matches(Iterable<int> ids);
@@ -20,10 +20,13 @@ class NotAssignedTagsQuery extends TagsQuery {
@override @override
bool matches(Iterable<int> ids) => ids.isEmpty; bool matches(Iterable<int> ids) => ids.isEmpty;
@override
List<Object?> get props => [];
} }
@HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery) @HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedTagsQuery)
class AnyAssignedTagsQuery extends TagsQuery with EquatableMixin { class AnyAssignedTagsQuery extends TagsQuery {
@HiveField(0) @HiveField(0)
final List<int> tagIds; final List<int> tagIds;
const AnyAssignedTagsQuery({ const AnyAssignedTagsQuery({
@@ -54,7 +57,7 @@ class AnyAssignedTagsQuery extends TagsQuery with EquatableMixin {
} }
@HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery) @HiveType(typeId: PaperlessApiHiveTypeIds.idsTagsQuery)
class IdsTagsQuery extends TagsQuery with EquatableMixin { class IdsTagsQuery extends TagsQuery {
@HiveField(0) @HiveField(0)
final List<int> include; final List<int> include;
@HiveField(1) @HiveField(1)

View File

@@ -1,3 +1,4 @@
import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/config/hive/hive_type_ids.dart'; import 'package:paperless_api/config/hive/hive_type_ids.dart';
@@ -91,6 +92,11 @@ class TextQuery {
return other.queryText == queryText && other.queryType == queryType; return other.queryText == queryText && other.queryType == queryType;
} }
@override
String toString() {
return "TextQuery($queryText, $queryType)";
}
@override @override
int get hashCode => Object.hash(queryText, queryType); int get hashCode => Object.hash(queryText, queryType);
} }

View File

@@ -1,9 +1,4 @@
import 'package:paperless_api/src/models/exception/exceptions.dart';
abstract class PaperlessAuthenticationApi { abstract class PaperlessAuthenticationApi {
///
/// @throws [PaperlessUnauthorizedException]
///
Future<String> login({ Future<String> login({
required String username, required String username,
required String password, required String password,

View File

@@ -37,6 +37,11 @@ class PaperlessAuthenticationApiImpl implements PaperlessAuthenticationApi {
// return AuthenticationTemporaryRedirect(redirectUrl!); // return AuthenticationTemporaryRedirect(redirectUrl!);
} on DioException catch (exception) { } on DioException catch (exception) {
throw exception.unravel(); throw exception.unravel();
} catch (error, stackTrace) {
throw PaperlessApiException.unknown(
details: error.toString(),
stackTrace: stackTrace,
);
} }
} }
} }

View File

@@ -36,4 +36,7 @@ abstract class PaperlessDocumentsApi {
Future<FieldSuggestions> findSuggestions(DocumentModel document); Future<FieldSuggestions> findSuggestions(DocumentModel document);
Future<List<String>> autocomplete(String query, [int limit = 10]); Future<List<String>> autocomplete(String query, [int limit = 10]);
Future<DocumentModel> addNote(
{required DocumentModel document, required String text});
} }

View File

@@ -337,7 +337,30 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
return document.copyWith(notes: notes); return document.copyWith(notes: notes);
} on DioException catch (exception) { } on DioException catch (exception) {
throw exception.unravel( throw exception.unravel(
orElse: const PaperlessApiException(ErrorCode.documentDeleteFailed), orElse: const PaperlessApiException(ErrorCode.deleteNoteFailed),
);
}
}
@override
Future<DocumentModel> addNote({
required DocumentModel document,
required String text,
}) async {
try {
final response = await client.post(
"/api/documents/${document.id}/notes/",
options: Options(validateStatus: (status) => status == 200),
data: {'note': text},
);
final notes =
(response.data as List).map((e) => NoteModel.fromJson(e)).toList();
return document.copyWith(notes: notes);
} on DioException catch (exception) {
throw exception.unravel(
orElse: const PaperlessApiException(ErrorCode.addNoteFailed),
); );
} }
} }

View File

@@ -22,6 +22,8 @@ dependencies:
jiffy: ^5.0.0 jiffy: ^5.0.0
freezed_annotation: ^2.4.1 freezed_annotation: ^2.4.1
hive: ^2.2.3 hive: ^2.2.3
mockito: ^5.4.4
http_mock_adapter: ^0.6.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@@ -0,0 +1,90 @@
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:mockito/mockito.dart';
import 'package:paperless_api/paperless_api.dart';
void main() {
group('AuthenticationApi with DioHttpErrorIncerceptor', () {
late PaperlessAuthenticationApi authenticationApi;
late DioAdapter mockAdapter;
const token = "abcde";
const invalidCredentialsServerMessage =
"Unable to log in with provided credentials.";
setUp(() {
final dio = Dio()..interceptors.add(DioHttpErrorInterceptor());
authenticationApi = PaperlessAuthenticationApiImpl(dio);
mockAdapter = DioAdapter(dio: dio);
// Valid credentials
mockAdapter.onPost(
"/api/token/",
data: {
"username": "username",
"password": "password",
},
(server) => server.reply(200, {"token": token}),
);
// Invalid credentials
mockAdapter.onPost(
"/api/token/",
data: {
"username": "wrongUsername",
"password": "wrongPassword",
},
(server) => server.reply(400, {
"non_field_errors": [invalidCredentialsServerMessage]
}),
);
});
// tearDown(() {});
test(
'should return a valid token when logging in with valid credentials',
() {
expect(
authenticationApi.login(
username: "username",
password: "password",
),
completion(token),
);
},
);
test(
'should throw a PaperlessFormValidationException containing a reason '
'when logging in with invalid credentials',
() {
expect(
authenticationApi.login(
username: "wrongUsername",
password: "wrongPassword",
),
throwsA(isA<PaperlessFormValidationException>().having(
(e) => e.unspecificErrorMessage(),
"non-field specific error message",
equals(invalidCredentialsServerMessage),
)),
);
},
);
test(
'should return an error when logging in with invalid credentials',
() {
expect(
authenticationApi.login(
username: "wrongUsername",
password: "wrongPassword",
),
throwsA(isA<PaperlessFormValidationException>().having(
(e) => e.unspecificErrorMessage(),
"non-field specific error message",
equals(invalidCredentialsServerMessage),
)),
);
},
);
});
}

View File

@@ -2,7 +2,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
void main() { void main() {
group('Validate parsing logic from [SavedView] to [DocumentFilter]:', () { group('Parsing [SavedView] to [DocumentFilter]:', () {
test('Values are correctly parsed if set.', () { test('Values are correctly parsed if set.', () {
expect( expect(
SavedView.fromJson({ SavedView.fromJson({
@@ -64,7 +64,7 @@ void main() {
] ]
}).toDocumentFilter(), }).toDocumentFilter(),
equals( equals(
DocumentFilter.initial.copyWith( DocumentFilter(
correspondent: const SetIdQueryParameter(id: 42), correspondent: const SetIdQueryParameter(id: 42),
documentType: const SetIdQueryParameter(id: 69), documentType: const SetIdQueryParameter(id: 69),
storagePath: const SetIdQueryParameter(id: 14), storagePath: const SetIdQueryParameter(id: 14),
@@ -83,6 +83,7 @@ void main() {
sortField: SortField.created, sortField: SortField.created,
sortOrder: SortOrder.descending, sortOrder: SortOrder.descending,
query: const TextQuery.extended("Never gonna give you up"), query: const TextQuery.extended("Never gonna give you up"),
selectedView: 1,
), ),
), ),
); );
@@ -99,7 +100,11 @@ void main() {
"sort_reverse": true, "sort_reverse": true,
"filter_rules": [], "filter_rules": [],
}).toDocumentFilter(), }).toDocumentFilter(),
equals(DocumentFilter.initial), equals(
const DocumentFilter(
selectedView: 1,
),
),
); );
}); });
@@ -130,11 +135,12 @@ void main() {
}, },
], ],
}).toDocumentFilter(); }).toDocumentFilter();
final expected = DocumentFilter.initial.copyWith( const expected = DocumentFilter(
correspondent: const NotAssignedIdQueryParameter(), correspondent: NotAssignedIdQueryParameter(),
documentType: const NotAssignedIdQueryParameter(), documentType: NotAssignedIdQueryParameter(),
storagePath: const NotAssignedIdQueryParameter(), storagePath: NotAssignedIdQueryParameter(),
tags: const NotAssignedTagsQuery(), tags: NotAssignedTagsQuery(),
selectedView: 1,
); );
expect( expect(
actual, actual,
@@ -148,6 +154,7 @@ void main() {
expect( expect(
SavedView.fromDocumentFilter( SavedView.fromDocumentFilter(
DocumentFilter( DocumentFilter(
selectedView: 1,
correspondent: const SetIdQueryParameter(id: 1), correspondent: const SetIdQueryParameter(id: 1),
documentType: const SetIdQueryParameter(id: 2), documentType: const SetIdQueryParameter(id: 2),
storagePath: const SetIdQueryParameter(id: 3), storagePath: const SetIdQueryParameter(id: 3),
@@ -173,6 +180,7 @@ void main() {
), ),
equals( equals(
SavedView( SavedView(
id: 1,
name: "test_name", name: "test_name",
showOnDashboard: false, showOnDashboard: false,
showInSidebar: false, showInSidebar: false,