From 98333a4b16899096bd4d20ddaee9c43e912767d9 Mon Sep 17 00:00:00 2001 From: mqus <8398165+mqus@users.noreply.github.com> Date: Thu, 22 Apr 2021 22:51:38 +0200 Subject: [PATCH] Increase test coverage (#539) * expand test coverage * More tests, fix Index.equals * test toString of ProcessorError --- .../processor/deletion_method_processor.dart | 19 +- .../error/change_method_processor_error.dart | 40 ++ .../processor/insertion_method_processor.dart | 21 +- .../processor/update_method_processor.dart | 26 +- floor_generator/lib/value_object/index.dart | 4 +- .../deletion_method_processor_test.dart | 116 ++++++ .../test/processor/entity_processor_test.dart | 354 ++++++++++++++++++ .../test/processor/field_processor_test.dart | 15 + .../insertion_method_processor_test.dart | 56 ++- .../test/processor/processor_error_test.dart | 34 ++ .../update_method_processor_test.dart | 73 +++- .../test/writer/database_writer_test.dart | 69 ++++ 12 files changed, 765 insertions(+), 62 deletions(-) create mode 100644 floor_generator/lib/processor/error/change_method_processor_error.dart create mode 100644 floor_generator/test/processor/deletion_method_processor_test.dart create mode 100644 floor_generator/test/processor/processor_error_test.dart diff --git a/floor_generator/lib/processor/deletion_method_processor.dart b/floor_generator/lib/processor/deletion_method_processor.dart index d4d64ca0..18f840a1 100644 --- a/floor_generator/lib/processor/deletion_method_processor.dart +++ b/floor_generator/lib/processor/deletion_method_processor.dart @@ -1,20 +1,22 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:floor_generator/misc/change_method_processor_helper.dart'; +import 'package:floor_generator/processor/error/change_method_processor_error.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/value_object/deletion_method.dart'; import 'package:floor_generator/value_object/entity.dart'; -import 'package:source_gen/source_gen.dart'; class DeletionMethodProcessor implements Processor { final MethodElement _methodElement; final ChangeMethodProcessorHelper _helper; + final ChangeMethodProcessorError _errors; DeletionMethodProcessor( final MethodElement methodElement, final List entities, [ final ChangeMethodProcessorHelper? changeMethodProcessorHelper, ]) : _methodElement = methodElement, + _errors = ChangeMethodProcessorError(methodElement, 'Deletion'), _helper = changeMethodProcessorHelper ?? ChangeMethodProcessorHelper(methodElement, entities); @@ -32,10 +34,7 @@ class DeletionMethodProcessor implements Processor { final returnsInt = flattenedReturnType.isDartCoreInt; if (!returnsVoid && !returnsInt) { - throw InvalidGenerationSourceError( - 'Deletion methods have to return a Future of either void or int.', - element: _methodElement, - ); + throw _errors.doesNotReturnVoidNorInt; } final parameterElement = _helper.getParameterElement(); @@ -60,19 +59,13 @@ class DeletionMethodProcessor implements Processor { void _assertMethodReturnsNoList(final DartType flattenedReturnType) { if (flattenedReturnType.isDartCoreList) { - throw InvalidGenerationSourceError( - 'Deletion methods have to return a Future of either void or int but not a list.', - element: _methodElement, - ); + throw _errors.shouldNotReturnList; } } void _assertMethodReturnsFuture(final DartType returnType) { if (!returnType.isDartAsyncFuture) { - throw InvalidGenerationSourceError( - 'Deletion methods have to return a Future.', - element: _methodElement, - ); + throw _errors.doesNotReturnFuture; } } } diff --git a/floor_generator/lib/processor/error/change_method_processor_error.dart b/floor_generator/lib/processor/error/change_method_processor_error.dart new file mode 100644 index 00000000..2d52d104 --- /dev/null +++ b/floor_generator/lib/processor/error/change_method_processor_error.dart @@ -0,0 +1,40 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:floor_generator/misc/constants.dart'; +import 'package:floor_annotation/floor_annotation.dart' as annotations + show OnConflictStrategy; +import 'package:source_gen/source_gen.dart'; + +class ChangeMethodProcessorError { + final MethodElement _methodElement; + final String _methodType; + + ChangeMethodProcessorError(this._methodElement, this._methodType); + + InvalidGenerationSourceError get doesNotReturnVoidNorInt => + InvalidGenerationSourceError( + '$_methodType methods have to return a Future of either void or int.', + element: _methodElement, + ); + + InvalidGenerationSourceError get doesNotReturnFuture => + InvalidGenerationSourceError( + '$_methodType methods have to return a Future.', + element: _methodElement, + ); + + InvalidGenerationSourceError get shouldNotReturnList => + InvalidGenerationSourceError( + '$_methodType methods have to return a Future of either void or int but not a list.', + element: _methodElement, + ); + InvalidGenerationSourceError get doesNotReturnVoidNorIntNorListInt => + InvalidGenerationSourceError( + '$_methodType methods have to return a Future of either void, int or List.', + element: _methodElement, + ); + InvalidGenerationSourceError get wrongOnConflictValue => + InvalidGenerationSourceError( + 'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}', + element: _methodElement, + ); +} diff --git a/floor_generator/lib/processor/insertion_method_processor.dart b/floor_generator/lib/processor/insertion_method_processor.dart index f8376588..2e34332b 100644 --- a/floor_generator/lib/processor/insertion_method_processor.dart +++ b/floor_generator/lib/processor/insertion_method_processor.dart @@ -1,25 +1,27 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations - show Insert, OnConflictStrategy; + show Insert; import 'package:floor_generator/misc/change_method_processor_helper.dart'; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/extension/dart_object_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; +import 'package:floor_generator/processor/error/change_method_processor_error.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/value_object/entity.dart'; import 'package:floor_generator/value_object/insertion_method.dart'; -import 'package:source_gen/source_gen.dart'; class InsertionMethodProcessor implements Processor { final MethodElement _methodElement; final ChangeMethodProcessorHelper _helper; + final ChangeMethodProcessorError _errors; InsertionMethodProcessor( final MethodElement methodElement, final List entities, [ final ChangeMethodProcessorHelper? changeMethodProcessorHelper, ]) : _methodElement = methodElement, + _errors = ChangeMethodProcessorError(methodElement, 'Insertion'), _helper = changeMethodProcessorHelper ?? ChangeMethodProcessorHelper(methodElement, entities); @@ -39,10 +41,7 @@ class InsertionMethodProcessor implements Processor { final returnsIntList = returnsList && flattenedReturnType.isDartCoreInt; if (!returnsVoid && !returnsIntList && !returnsInt) { - throw InvalidGenerationSourceError( - 'Insertion methods have to return a Future of either void, int or List.', - element: _methodElement, - ); + throw _errors.doesNotReturnVoidNorIntNorListInt; } final parameterElement = _helper.getParameterElement(); @@ -83,10 +82,7 @@ class InsertionMethodProcessor implements Processor { ?.toEnumValueString(); if (onConflictStrategy == null) { - throw InvalidGenerationSourceError( - 'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}', - element: _methodElement, - ); + throw _errors.wrongOnConflictValue; } else { return onConflictStrategy; } @@ -94,10 +90,7 @@ class InsertionMethodProcessor implements Processor { void _assertMethodReturnsFuture(final DartType returnType) { if (!returnType.isDartAsyncFuture) { - throw InvalidGenerationSourceError( - 'Insertion methods have to return a Future.', - element: _methodElement, - ); + throw _errors.doesNotReturnFuture; } } } diff --git a/floor_generator/lib/processor/update_method_processor.dart b/floor_generator/lib/processor/update_method_processor.dart index ab127fd1..fe323029 100644 --- a/floor_generator/lib/processor/update_method_processor.dart +++ b/floor_generator/lib/processor/update_method_processor.dart @@ -1,25 +1,27 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations - show Update, OnConflictStrategy; + show Update; import 'package:floor_generator/misc/change_method_processor_helper.dart'; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/extension/dart_object_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; +import 'package:floor_generator/processor/error/change_method_processor_error.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/value_object/entity.dart'; import 'package:floor_generator/value_object/update_method.dart'; -import 'package:source_gen/source_gen.dart'; class UpdateMethodProcessor implements Processor { final MethodElement _methodElement; final ChangeMethodProcessorHelper _helper; + final ChangeMethodProcessorError _errors; UpdateMethodProcessor( final MethodElement methodElement, final List entities, [ final ChangeMethodProcessorHelper? changeMethodProcessorHelper, ]) : _methodElement = methodElement, + _errors = ChangeMethodProcessorError(methodElement, 'Update'), _helper = changeMethodProcessorHelper ?? ChangeMethodProcessorHelper(methodElement, entities); @@ -37,10 +39,7 @@ class UpdateMethodProcessor implements Processor { final returnsVoid = flattenedReturnType.isVoid; if (!returnsInt && !returnsVoid) { - throw InvalidGenerationSourceError( - 'Update methods have to return a Future of either void or int.', - element: _methodElement, - ); + throw _errors.doesNotReturnVoidNorInt; } final parameterElement = _helper.getParameterElement(); @@ -67,10 +66,7 @@ class UpdateMethodProcessor implements Processor { ?.toEnumValueString(); if (onConflictStrategy == null) { - throw InvalidGenerationSourceError( - 'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}', - element: _methodElement, - ); + throw _errors.wrongOnConflictValue; } else { return onConflictStrategy; } @@ -82,19 +78,13 @@ class UpdateMethodProcessor implements Processor { void _assertMethodReturnsNoList(final DartType flattenedReturnType) { if (flattenedReturnType.isDartCoreList) { - throw InvalidGenerationSourceError( - 'Update methods have to return a Future of either void or int but not a list.', - element: _methodElement, - ); + throw _errors.shouldNotReturnList; } } void _assertMethodReturnsFuture(final DartType returnType) { if (!returnType.isDartAsyncFuture) { - throw InvalidGenerationSourceError( - 'Update methods have to return a Future.', - element: _methodElement, - ); + throw _errors.doesNotReturnFuture; } } } diff --git a/floor_generator/lib/value_object/index.dart b/floor_generator/lib/value_object/index.dart index b3422d27..2d59cf68 100644 --- a/floor_generator/lib/value_object/index.dart +++ b/floor_generator/lib/value_object/index.dart @@ -1,3 +1,5 @@ +import 'package:collection/collection.dart'; + class Index { final String name; final String tableName; @@ -25,7 +27,7 @@ class Index { name == other.name && tableName == other.tableName && unique == other.unique && - columnNames == other.columnNames; + const ListEquality().equals(columnNames, other.columnNames); @override int get hashCode => diff --git a/floor_generator/test/processor/deletion_method_processor_test.dart b/floor_generator/test/processor/deletion_method_processor_test.dart new file mode 100644 index 00000000..1a1d7eb4 --- /dev/null +++ b/floor_generator/test/processor/deletion_method_processor_test.dart @@ -0,0 +1,116 @@ +import 'package:floor_generator/processor/deletion_method_processor.dart'; +import 'package:floor_generator/processor/error/change_method_processor_error.dart'; +import 'package:source_gen/source_gen.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; + +void main() { + group('expected errors', () { + test('when not accepting Parameter', () async { + final deletionMethod = await ''' + @delete + Future deletePerson(); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => DeletionMethodProcessor(deletionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError(InvalidGenerationSourceError( + 'There is no parameter supplied for this method. Please add one.', + element: deletionMethod, + ))); + }); + test('when accepting more than one Parameter', () async { + final deletionMethod = await ''' + @delete + Future deletePerson(Person p1, Person p2); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => DeletionMethodProcessor(deletionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError(InvalidGenerationSourceError( + 'Only one parameter is allowed on this.', + element: deletionMethod, + ))); + }); + test('when not accepting an Entity', () async { + final deletionMethod = await ''' + @delete + Future deletePerson(int p2); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => DeletionMethodProcessor(deletionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError(InvalidGenerationSourceError( + 'You are trying to change an object which is not an entity.', + element: deletionMethod, + ))); + }); + test('when not returning Future', () async { + final deletionMethod = await ''' + @delete + void deletePerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => DeletionMethodProcessor(deletionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(deletionMethod, 'Deletion') + .doesNotReturnFuture)); + }); + test('when returning a List', () async { + final deletionMethod = await ''' + @delete + Future> deletePerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => DeletionMethodProcessor(deletionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(deletionMethod, 'Deletion') + .shouldNotReturnList)); + }); + test('when not returning int or void', () async { + final deletionMethod = await ''' + @delete + Future deletePerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => DeletionMethodProcessor(deletionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(deletionMethod, 'Deletion') + .doesNotReturnVoidNorInt)); + }); + }); +} diff --git a/floor_generator/test/processor/entity_processor_test.dart b/floor_generator/test/processor/entity_processor_test.dart index 43e2a2e5..476de063 100644 --- a/floor_generator/test/processor/entity_processor_test.dart +++ b/floor_generator/test/processor/entity_processor_test.dart @@ -56,6 +56,54 @@ void main() { expect(actual, equals(expected)); }); + test( + 'Process entity with null fields falls back to defaults (should be prevented by non-nullable types)', + () async { + final classElement = await createClassElement(''' + @Entity( + tableName:null, + foreignKeys:null, + indices:null, + primaryKeys:null, + withoutRowid:null, + ) + @Fts3(tokenizerArgs:null) + class Person { + @primaryKey + final int id; + + final String name; + + Person(this.id, this.name); + } + '''); + + final actual = EntityProcessor(classElement, {}).process(); + + const name = 'Person'; + final fields = classElement.fields + .map((fieldElement) => FieldProcessor(fieldElement, null).process()) + .toList(); + final primaryKey = PrimaryKey([fields[0]], false); + const foreignKeys = []; + const indices = []; + const constructor = "Person(row['id'] as int, row['name'] as String)"; + const valueMapping = "{'id': item.id, 'name': item.name}"; + final expected = Entity( + classElement, + name, + fields, + primaryKey, + foreignKeys, + indices, + false, + constructor, + valueMapping, + Fts3(annotations.FtsTokenizer.simple, []), + ); + expect(actual, equals(expected)); + }); + test('Process entity with compound primary key', () async { final classElement = await createClassElement(''' @Entity(primaryKeys: ['id', 'name']) @@ -94,6 +142,48 @@ void main() { expect(actual, equals(expected)); }); + test('Process entity with index', () async { + final classElement = await createClassElement(''' + @Entity(indices: [Index(name:'i1', unique: true, value:['id']),Index(unique: false, value:['id','name'])]) + class Person { + @primaryKey + final int id; + + final String name; + + Person(this.id, this.name); + } + '''); + + final actual = EntityProcessor(classElement, {}).process(); + + const name = 'Person'; + final fields = classElement.fields + .map((fieldElement) => FieldProcessor(fieldElement, null).process()) + .toList(); + final primaryKey = PrimaryKey(fields.sublist(0, 1), false); + const foreignKeys = []; + final indices = [ + Index('i1', 'Person', true, ['id']), + Index('index_Person_id_name', 'Person', false, ['id', 'name']) + ]; + const constructor = "Person(row['id'] as int, row['name'] as String)"; + const valueMapping = "{'id': item.id, 'name': item.name}"; + final expected = Entity( + classElement, + name, + fields, + primaryKey, + foreignKeys, + indices, + false, + constructor, + valueMapping, + null, + ); + expect(actual, equals(expected)); + }); + group('foreign keys', () { test('foreign key holds correct values', () async { final classElements = await _createClassElements(''' @@ -322,6 +412,270 @@ void main() { expect(actual, equals(expected)); }); }); + + group('expected errors', () { + test('missing primary key', () async { + final classElements = await createClassElement(''' + @entity + class Person { + final int id; + + final String name; + + Person(this.id, this.name); + } + '''); + + final processor = EntityProcessor(classElements, {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElements).missingPrimaryKey)); + }); + test('compound primary key mismatch', () async { + final classElements = await createClassElement(''' + @Entity( + primaryKeys:['notAField'] + ) + class Person { + final int id; + + final String name; + + Person(this.id, this.name); + } + '''); + + final processor = EntityProcessor(classElements, {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElements).missingPrimaryKey)); + }); + test('missing parent columns', () async { + final classElements = await _createClassElements(''' + @entity + class Person { + @primaryKey + final int id; + + final String name; + + Person(this.id, this.name); + } + + @Entity( + foreignKeys: [ + ForeignKey( + childColumns: ['owner_id'], + parentColumns: [], + entity: Person, + onUpdate: null + onDelete: ForeignKeyAction.setNull, + ) + ], + ) + class Dog { + @primaryKey + final int id; + + final String name; + + @ColumnInfo(name: 'owner_id') + final int ownerId; + + Dog(this.id, this.name, this.ownerId); + } + '''); + + final processor = EntityProcessor(classElements[1], {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElements[1]).missingParentColumns)); + }); + test('missing child columns', () async { + final classElements = await _createClassElements(''' + @entity + class Person { + @primaryKey + final int id; + + final String name; + + Person(this.id, this.name); + } + + @Entity( + foreignKeys: [ + ForeignKey( + childColumns: [], + parentColumns: ['id'], + entity: Person, + onUpdate: null + onDelete: ForeignKeyAction.setNull, + ) + ], + ) + class Dog { + @primaryKey + final int id; + + final String name; + + @ColumnInfo(name: 'owner_id') + final int ownerId; + + Dog(this.id, this.name, this.ownerId); + } + '''); + + final processor = EntityProcessor(classElements[1], {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElements[1]).missingChildColumns)); + }); + test('foreignKey does not reference entity', () async { + final classElements = await _createClassElements(''' + final Person = ()=>2; + + @Entity( + foreignKeys: [ + ForeignKey( + childColumns: ['owner_id'], + parentColumns: ['id'], + entity: Entity(), + onUpdate: ForeignKeyAction.setNull + onDelete: ForeignKeyAction.setNull, + ) + ], + ) + class Dog { + @primaryKey + final int id; + + final String name; + + @ColumnInfo(name: 'owner_id') + final int ownerId; + + Dog(this.id, this.name, this.ownerId); + } + '''); + + final processor = EntityProcessor(classElements[0], {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElements[0]) + .foreignKeyDoesNotReferenceEntity)); + }, skip: 'Can not reproduce error case'); + test('foreign key reference does not exist', () async { + final classElements = await createClassElement(''' + @Entity( + foreignKeys: [ + ForeignKey( + childColumns: ['owner_id'], + parentColumns: ['id'], + entity: Person, + onUpdate: null + onDelete: ForeignKeyAction.setNull, + ) + ], + ) + class Dog { + @primaryKey + final int id; + + final String name; + + @ColumnInfo(name: 'owner_id') + final int ownerId; + + Dog(this.id, this.name, this.ownerId); + } + '''); + + final processor = EntityProcessor(classElements, {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElements).foreignKeyNoEntity)); + }); + test('missing index column name', () async { + final classElement = await createClassElement(''' + @Entity( + indices:[Index(value:[])] + ) + class Dog { + @primaryKey + final int id; + + final String name; + + @ColumnInfo(name: 'owner_id') + final int ownerId; + + Dog(this.id, this.name, this.ownerId); + } + '''); + + final processor = EntityProcessor(classElement, {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElement).missingIndexColumnName)); + }); + test('no matching index column', () async { + final classElement = await createClassElement(''' + @Entity( + indices:[Index(value:['notAColumn'])] + ) + class Dog { + @primaryKey + final int id; + + final String name; + + @ColumnInfo(name: 'owner_id') + final int ownerId; + + Dog(this.id, this.name, this.ownerId); + } + '''); + + final processor = EntityProcessor(classElement, {}); + expect( + processor.process, + throwsInvalidGenerationSourceError(EntityProcessorError(classElement) + .noMatchingColumn(['notAColumn']))); + }); + test('auto-increment not usable with `WITHOUT ROWID`', () async { + final classElement = await createClassElement(''' + @Entity( + withoutRowid:true + ) + class Dog { + @PrimaryKey(autoGenerate:true) + final int id; + + final String name; + + @ColumnInfo(name: 'owner_id') + final int ownerId; + + Dog(this.id, this.name, this.ownerId); + } + '''); + + final processor = EntityProcessor(classElement, {}); + expect( + processor.process, + throwsInvalidGenerationSourceError( + EntityProcessorError(classElement).autoIncrementInWithoutRowid)); + }); + }); } Future> _createClassElements(final String classes) async { diff --git a/floor_generator/test/processor/field_processor_test.dart b/floor_generator/test/processor/field_processor_test.dart index 32fe695e..7d8c80b7 100644 --- a/floor_generator/test/processor/field_processor_test.dart +++ b/floor_generator/test/processor/field_processor_test.dart @@ -8,6 +8,7 @@ import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; import '../dart_type.dart'; +import '../test_utils.dart'; void main() { test('Process field', () async { @@ -169,6 +170,20 @@ void main() { ); expect(actual, equals(expected)); }); + test('Field with unsupported type throws error', () async { + final fieldElement = await _generateFieldElement(''' + final List id; + '''); + + expect( + FieldProcessor(fieldElement, null).process, + throwsInvalidGenerationSourceError(InvalidGenerationSourceError( + 'Column type is not supported for List.', + todo: + 'Either make to use a supported type or supply a type converter.', + element: fieldElement, + ))); + }); } Future _generateFieldElement(final String field) async { diff --git a/floor_generator/test/processor/insertion_method_processor_test.dart b/floor_generator/test/processor/insertion_method_processor_test.dart index 5c62e525..1eabf2e8 100644 --- a/floor_generator/test/processor/insertion_method_processor_test.dart +++ b/floor_generator/test/processor/insertion_method_processor_test.dart @@ -1,5 +1,5 @@ +import 'package:floor_generator/processor/error/change_method_processor_error.dart'; import 'package:floor_generator/processor/insertion_method_processor.dart'; -import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; import '../test_utils.dart'; @@ -20,17 +20,57 @@ void main() { expect(actual, equals('OnConflictStrategy.replace')); }); - test('Error on wrong onConflict value', () async { - final insertionMethod = await ''' + group('expected errors', () { + test('on wrong onConflict value', () async { + final insertionMethod = await ''' @Insert(onConflict: OnConflictStrategy.doesnotexist) Future insertPerson(Person person); ''' - .asDaoMethodElement(); - final entities = await getPersonEntity(); + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => InsertionMethodProcessor(insertionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(insertionMethod, 'Insertion') + .wrongOnConflictValue)); + }); + test('when not returning Future', () async { + final insertionMethod = await ''' + @insert + void insertPerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => InsertionMethodProcessor(insertionMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(insertionMethod, 'Insertion') + .doesNotReturnFuture)); + }); + test('when not returning int or void or List', () async { + final insertionMethod = await ''' + @insert + Future insertPerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); - final actual = - () => InsertionMethodProcessor(insertionMethod, [entities]).process(); + final actual = + () => InsertionMethodProcessor(insertionMethod, [entities]).process(); - expect(actual, throwsA(const TypeMatcher())); + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(insertionMethod, 'Insertion') + .doesNotReturnVoidNorIntNorListInt)); + }); }); } diff --git a/floor_generator/test/processor/processor_error_test.dart b/floor_generator/test/processor/processor_error_test.dart new file mode 100644 index 00000000..84eedd39 --- /dev/null +++ b/floor_generator/test/processor/processor_error_test.dart @@ -0,0 +1,34 @@ +import 'package:floor_generator/processor/error/processor_error.dart'; +import 'package:test/test.dart'; + +import '../fakes.dart'; +import '../test_utils.dart'; + +void main() { + test('toString with source element', () async { + final insertionMethod = await ''' + @Insert(onConflict: OnConflictStrategy.replace) + Future insertPerson(Person person); + ''' + .asDaoMethodElement(); + final error = ProcessorError( + message: 'mymessage', todo: 'mytodo', element: insertionMethod); + expect( + error.toString(), + equals('mymessage mytodo\n' + 'package:_resolve_source/_resolve_source.dart:8:20\n' + ' ╷\n' + '8 │ Future insertPerson(Person person);\n' + ' │ ^^^^^^^^^^^^\n' + ' ╵')); + }); + test('toString with empty source element', () async { + final element = FakeClassElement(); + final error = + ProcessorError(message: 'mymessage', todo: 'mytodo', element: element); + expect( + error.toString(), + equals('mymessage mytodo\n' + 'Cause: Instance of \'FakeClassElement\'\n')); + }); +} diff --git a/floor_generator/test/processor/update_method_processor_test.dart b/floor_generator/test/processor/update_method_processor_test.dart index 3e6abeb0..58789451 100644 --- a/floor_generator/test/processor/update_method_processor_test.dart +++ b/floor_generator/test/processor/update_method_processor_test.dart @@ -1,5 +1,5 @@ +import 'package:floor_generator/processor/error/change_method_processor_error.dart'; import 'package:floor_generator/processor/update_method_processor.dart'; -import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; import '../test_utils.dart'; @@ -19,17 +19,74 @@ void main() { expect(actual, equals('OnConflictStrategy.replace')); }); - test('Error on wrong onConflict value', () async { - final insertionMethod = await ''' + group('expected errors', () { + test('on wrong onConflict value', () async { + final updateMethod = await ''' @Update(onConflict: OnConflictStrategy.doesnotexist) Future updatePerson(Person person); ''' - .asDaoMethodElement(); - final entities = await getPersonEntity(); + .asDaoMethodElement(); + final entities = await getPersonEntity(); - final actual = - () => UpdateMethodProcessor(insertionMethod, [entities]).process(); + final actual = + () => UpdateMethodProcessor(updateMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(updateMethod, 'Update') + .wrongOnConflictValue)); + }); + test('when not returning Future', () async { + final updateMethod = await ''' + @update + void updatePerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => UpdateMethodProcessor(updateMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(updateMethod, 'Update') + .doesNotReturnFuture)); + }); + test('when returning a List', () async { + final updateMethod = await ''' + @update + Future> updatePerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => UpdateMethodProcessor(updateMethod, [entities]).process(); + + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(updateMethod, 'Update') + .shouldNotReturnList)); + }); + test('when not returning int or void', () async { + final updateMethod = await ''' + @update + Future updatePerson(Person person); + ''' + .asDaoMethodElement(); + final entities = await getPersonEntity(); + + final actual = + () => UpdateMethodProcessor(updateMethod, [entities]).process(); - expect(actual, throwsA(const TypeMatcher())); + expect( + actual, + throwsInvalidGenerationSourceError( + ChangeMethodProcessorError(updateMethod, 'Update') + .doesNotReturnVoidNorInt)); + }); }); } diff --git a/floor_generator/test/writer/database_writer_test.dart b/floor_generator/test/writer/database_writer_test.dart index b30f77db..a4ecaec1 100644 --- a/floor_generator/test/writer/database_writer_test.dart +++ b/floor_generator/test/writer/database_writer_test.dart @@ -66,6 +66,75 @@ void main() { ''')); }); + test('open database with DAO', () async { + final database = await _createDatabase(''' + @Database(version: 1, entities: [Person]) + abstract class TestDatabase extends FloorDatabase { + TestDao get testDao; + } + + @entity + class Person { + @primaryKey + final int id; + + final String name; + + Person(this.id, this.name); + } + + @dao + abstract class TestDao { + @insert + Future insertPersonWithReturn(Person person); + } + '''); + + final actual = DatabaseWriter(database).write(); + + expect(actual, equalsDart(r''' + class _$TestDatabase extends TestDatabase { + _$TestDatabase([StreamController? listener]) { + changeListener = listener ?? StreamController.broadcast(); + } + + TestDao? _testDaoInstance; + + Future open(String path, List migrations, + [Callback? callback]) async { + final databaseOptions = sqflite.OpenDatabaseOptions( + version: 1, + onConfigure: (database) async { + await database.execute('PRAGMA foreign_keys = ON'); + await callback?.onConfigure?.call(database); + }, + onOpen: (database) async { + await callback?.onOpen?.call(database); + }, + onUpgrade: (database, startVersion, endVersion) async { + await MigrationAdapter.runMigrations( + database, startVersion, endVersion, migrations); + + await callback?.onUpgrade?.call(database, startVersion, endVersion); + }, + onCreate: (database, version) async { + await database.execute( + 'CREATE TABLE IF NOT EXISTS `Person` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY (`id`))'); + + await callback?.onCreate?.call(database, version); + }, + ); + return sqfliteDatabaseFactory.openDatabase(path, options: databaseOptions); + } + + @override + TestDao get testDao { + return _testDaoInstance ??= _$TestDao(database, changeListener); + } + } + ''')); + }); + test('open database for complex entity', () async { final database = await _createDatabase(''' @Database(version: 1, entities: [Person])