From 190fbbfc918679a7cea9b13b930886129910ff84 Mon Sep 17 00:00:00 2001 From: Chima Precious Date: Fri, 26 Jan 2024 09:39:33 +0000 Subject: [PATCH] feat: Rework code structure (#19) * some refactoring * setup example swagger documentation route * simplify swagger sample * rework code * fix failing tests --- bin/backend.dart | 9 +- bin/tools/migrator.dart | 14 +- config/app.dart | 107 -------------- config/database.dart | 32 ----- database/config.dart | 28 ++++ lib/app/app.dart | 12 -- lib/app/http/kernel.dart | 29 ---- .../middlewares/api_auth_middleware.dart | 0 .../middlewares/core_middleware.dart | 6 +- lib/app/middlewares/middlewares.dart | 2 + lib/app/providers/provide_routes.dart | 10 +- lib/app/routes/api.dart | 4 +- lib/app/routes/web.dart | 2 +- lib/backend.dart | 65 +++++++++ lib/src/services/article_service.dart | 2 +- pubspec.lock | 61 ++++++-- pubspec.yaml | 8 +- test/backend_test.dart | 131 +++++++++++------- 18 files changed, 248 insertions(+), 274 deletions(-) delete mode 100644 config/app.dart delete mode 100644 config/database.dart create mode 100644 database/config.dart delete mode 100644 lib/app/app.dart delete mode 100644 lib/app/http/kernel.dart rename lib/app/{http => }/middlewares/api_auth_middleware.dart (100%) rename lib/app/{http => }/middlewares/core_middleware.dart (78%) create mode 100644 lib/app/middlewares/middlewares.dart create mode 100644 lib/backend.dart diff --git a/bin/backend.dart b/bin/backend.dart index 2109125..c42154c 100644 --- a/bin/backend.dart +++ b/bin/backend.dart @@ -1,16 +1,13 @@ import 'package:yaroorm/yaroorm.dart'; -import 'package:backend/app/app.dart'; +import 'package:backend/backend.dart'; -import '../config/app.dart' as app; -import '../config/database.dart' as db; +import '../database/config.dart' as orm; import 'backend.reflectable.dart'; -final blogApp = App(app.config); - void main(List arguments) async { initializeReflectable(); - DB.init(db.config); + DB.init(orm.config); await blogApp.bootstrap(); } diff --git a/bin/tools/migrator.dart b/bin/tools/migrator.dart index 5e791d2..471bbbc 100644 --- a/bin/tools/migrator.dart +++ b/bin/tools/migrator.dart @@ -1,18 +1,12 @@ -import 'package:yaroorm/migration/cli.dart'; -import 'package:yaroorm/yaroorm.dart'; - -import '../../config/database.dart' as db; +// ignore: depend_on_referenced_packages +import 'package:yaroo_cli/orm/runner.dart'; +import '../../database/config.dart' as orm; import 'migrator.reflectable.dart'; export 'package:backend/src/models/models.dart'; void main(List args) async { - if (args.isEmpty) return print('Nothing to do here'); - initializeReflectable(); - - DB.init(db.config); - - await MigratorCLI.processCmd(args[0], cmdArguments: args.sublist(1)); + await OrmCLIRunner.start(args, orm.config); } diff --git a/config/app.dart b/config/app.dart deleted file mode 100644 index 46811d5..0000000 --- a/config/app.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:backend/app/app.dart'; -import 'package:uuid/v4.dart'; -import 'package:yaroo/http/http.dart'; -import 'package:yaroo/yaroo.dart'; - -final config = AppConfig.fromJson({ - /* - |-------------------------------------------------------------------------- - | Application Name - |-------------------------------------------------------------------------- - | - */ - 'name': env('APP_NAME', defaultValue: 'The Yaroo blog'), - - /* - |-------------------------------------------------------------------------- - | Application Environment - |-------------------------------------------------------------------------- - | - | This value determines the "environment" your application is currently - | running in. This may determine how you prefer to configure various - | services the application utilizes. Set this in your ".env" file. - | - */ - - 'env': env('APP_ENV', defaultValue: 'development'), - - /* - |-------------------------------------------------------------------------- - | Application Debug Mode - |-------------------------------------------------------------------------- - | - */ - - 'debug': env('APP_DEBUG', defaultValue: true), - - /* - |-------------------------------------------------------------------------- - | Application URL - |-------------------------------------------------------------------------- - | - */ - - 'url': env('APP_URL', defaultValue: 'http://localhost'), - - /* - |-------------------------------------------------------------------------- - | Application Port - |-------------------------------------------------------------------------- - | - | The port your app will run on. If you don't provide this, the port - | in URL will be used. - | - */ - - 'port': env('APP_PORT', defaultValue: 80), - - /* - |-------------------------------------------------------------------------- - | Application Timezone - |-------------------------------------------------------------------------- - | - */ - - 'timezone': 'UTC', - - /* - |-------------------------------------------------------------------------- - | Application Locale Configuration - |-------------------------------------------------------------------------- - | - | The application locale determines the default locale that will be used - | by the translation service provider. You are free to set this value - | to any of the locales which will be supported by the application. - | - */ - - 'locale': 'en', - - /* - |-------------------------------------------------------------------------- - | Encryption Key - |-------------------------------------------------------------------------- - | - */ - - 'key': env('APP_KEY', defaultValue: UuidV4().generate()), - - /* - |-------------------------------------------------------------------------- - | Autoloaded Service Providers - |-------------------------------------------------------------------------- - | - | The service providers listed here will be automatically loaded on the - | request to your application. Feel free to add your own services to - | this array to grant expanded functionality to your applications. - | - */ - - 'providers': ServiceProvider.defaultProviders - ..addAll([ - CoreProvider, - RouteServiceProvider, - DatabaseServiceProvider, - BlogServiceProvider, - ]), -}); diff --git a/config/database.dart b/config/database.dart deleted file mode 100644 index 6afcc88..0000000 --- a/config/database.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:path/path.dart' as path; -import 'package:yaroo/yaroo.dart'; -import 'package:yaroorm/config.dart'; -import 'package:yaroorm/yaroorm.dart'; - -import '../database/migrations/create_articles_table.dart'; -import '../database/migrations/create_users_table.dart'; - -final config = YaroormConfig( - env('DB_CONNECTION', defaultValue: 'sqlite'), - connections: [ - DatabaseConnection( - 'sqlite', - DatabaseDriverType.sqlite, - database: env( - 'DB_DATABASE', - defaultValue: path.absolute('database', 'db.sqlite'), - ), - ), - DatabaseConnection( - 'mysql', - DatabaseDriverType.mysql, - port: env('DB_PORT', defaultValue: 0), - host: env('DB_HOST', defaultValue: ''), - username: env('DB_USERNAME', defaultValue: ''), - password: env('DB_PASSWORD', defaultValue: ''), - database: env('DB_DATABASE', defaultValue: ''), - secure: true, - ), - ], - migrations: [CreateUsersTable(), CreateArticlesTable()], -); diff --git a/database/config.dart b/database/config.dart new file mode 100644 index 0000000..731c9a2 --- /dev/null +++ b/database/config.dart @@ -0,0 +1,28 @@ +import 'package:path/path.dart' as path; +import 'package:yaroo/yaroo.dart'; +import 'package:yaroorm/yaroorm.dart'; + +import './migrations/create_articles_table.dart'; +import './migrations/create_users_table.dart'; + +final config = YaroormConfig( + env('DB_CONNECTION', 'test_db'), + connections: [ + DatabaseConnection( + 'test_db', + DatabaseDriverType.sqlite, + database: env('DB_DATABASE', path.absolute('database', 'db.sqlite')), + ), + DatabaseConnection( + 'mysql', + DatabaseDriverType.mysql, + port: env('DB_PORT', 0), + host: env('DB_HOST', ''), + username: env('DB_USERNAME', ''), + password: env('DB_PASSWORD', ''), + database: env('DB_DATABASE', ''), + secure: true, + ), + ], + migrations: [CreateUsersTable(), CreateArticlesTable()], +); diff --git a/lib/app/app.dart b/lib/app/app.dart deleted file mode 100644 index 67f0683..0000000 --- a/lib/app/app.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:yaroo/yaroo.dart'; - -export 'providers/providers.dart'; -import 'http/kernel.dart'; - -export '../src/controllers/controllers.dart'; -export '../src/models/models.dart'; -export '../src/models/dto/dto.dart'; - -class App extends ApplicationFactory { - App(AppConfig appConfig) : super(Kernel(), appConfig); -} diff --git a/lib/app/http/kernel.dart b/lib/app/http/kernel.dart deleted file mode 100644 index 3fc95ed..0000000 --- a/lib/app/http/kernel.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:yaroo/http/http.dart'; -import 'package:yaroo/http/kernel.dart' as prefix01; -import 'package:yaroo/http/meta.dart'; - -import 'middlewares/api_auth_middleware.dart'; -import 'middlewares/core_middleware.dart'; - -class Kernel extends prefix01.Kernel { - @override - List get middleware => [CoreMiddleware]; - - @override - Map> get middlewareGroups => { - 'web': [], - 'auth:api': [ApiAuthMiddleware], - }; - - @override - FutureOr onApplicationException(Object error, Request request, Response response) { - if (error is RequestValidationError) { - return response.json(error.errorBody, statusCode: HttpStatus.badRequest); - } - - return super.onApplicationException(error, request, response); - } -} diff --git a/lib/app/http/middlewares/api_auth_middleware.dart b/lib/app/middlewares/api_auth_middleware.dart similarity index 100% rename from lib/app/http/middlewares/api_auth_middleware.dart rename to lib/app/middlewares/api_auth_middleware.dart diff --git a/lib/app/http/middlewares/core_middleware.dart b/lib/app/middlewares/core_middleware.dart similarity index 78% rename from lib/app/http/middlewares/core_middleware.dart rename to lib/app/middlewares/core_middleware.dart index a36eaf3..b35d2c9 100644 --- a/lib/app/http/middlewares/core_middleware.dart +++ b/lib/app/middlewares/core_middleware.dart @@ -16,7 +16,11 @@ class CoreMiddleware extends Middleware { next(); } - _webMdw = loggerMdw.chain(cookieParserMdw); + if (app.config.environment == 'development') { + _webMdw = loggerMdw.chain(cookieParserMdw); + } else { + _webMdw = cookieParserMdw; + } } @override diff --git a/lib/app/middlewares/middlewares.dart b/lib/app/middlewares/middlewares.dart new file mode 100644 index 0000000..b96613a --- /dev/null +++ b/lib/app/middlewares/middlewares.dart @@ -0,0 +1,2 @@ +export 'api_auth_middleware.dart'; +export 'core_middleware.dart'; diff --git a/lib/app/providers/provide_routes.dart b/lib/app/providers/provide_routes.dart index 07d6102..fe3b326 100644 --- a/lib/app/providers/provide_routes.dart +++ b/lib/app/providers/provide_routes.dart @@ -1,9 +1,9 @@ import 'dart:async'; +import 'package:backend/backend.dart'; import 'package:yaroo/http/http.dart'; import 'package:yaroo/yaroo.dart'; -import '../../src/controllers/controllers.dart'; import '../routes/api.dart' as api; import '../routes/web.dart' as web; @@ -15,24 +15,24 @@ class RouteServiceProvider extends ServiceProvider { /*|-------------------------------------------------------------------------- | API Routes |--------------------------------------------------------------------------*/ - Route.group('api').routes([ + Route.group('api', [ Route.post('/auth/login', (AuthController, #login)), Route.post('/auth/register', (AuthController, #register)), Route.get('/users/', (UserController, #show)), /// get articles and detail without auth - Route.group('articles').routes([ + Route.group('articles', [ Route.get('/', (ArticleController, #index)), Route.get('/', (ArticleController, #show)), ]), ]), - Route.middleware('auth:api').group('api').routes(api.routes), + Route.middleware('api:auth').group('api', api.routes), /*|-------------------------------------------------------------------------- | Web Routes |--------------------------------------------------------------------------*/ - Route.middleware('web').group('/').routes(web.routes), + Route.middleware('web').group('/', web.routes), ], ); } diff --git a/lib/app/routes/api.dart b/lib/app/routes/api.dart index 3345856..b72b478 100644 --- a/lib/app/routes/api.dart +++ b/lib/app/routes/api.dart @@ -3,13 +3,13 @@ import 'package:yaroo/yaroo.dart'; List routes = [ /// Users - Route.group('users').routes([ + Route.group('users', [ Route.get('/', (UserController, #index)), Route.get('/me', (UserController, #currentUser)), ]), /// Articles - Route.group('articles').routes([ + Route.group('articles', [ Route.post('/', (ArticleController, #create)), Route.put('/', (ArticleController, #update)), Route.delete('/', (ArticleController, #delete)), diff --git a/lib/app/routes/web.dart b/lib/app/routes/web.dart index 1dba824..6f294a8 100644 --- a/lib/app/routes/web.dart +++ b/lib/app/routes/web.dart @@ -2,5 +2,5 @@ import 'package:yaroo/http/http.dart'; import 'package:yaroo/yaroo.dart'; final routes = [ - Route.handler(HTTPMethod.ALL, '*', (_, req, res) => res.ok('hey 🚀')), + Route.route(HTTPMethod.GET, '/', (req, res) => res.ok('Hello World 🤘')), ]; diff --git a/lib/backend.dart b/lib/backend.dart new file mode 100644 index 0000000..dfbfd83 --- /dev/null +++ b/lib/backend.dart @@ -0,0 +1,65 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:uuid/v4.dart'; +import 'package:yaroo/http/http.dart'; +import 'package:yaroo/http/meta.dart'; +import 'package:yaroo/yaroo.dart'; + +import 'app/middlewares/core_middleware.dart'; +import 'app/middlewares/api_auth_middleware.dart'; +import 'app/providers/providers.dart'; + +export 'src/controllers/controllers.dart'; +export 'src/models/models.dart'; +export 'src/models/dto/dto.dart'; + +bool get isTestMode { + var isDebug = false; + assert(() { + isDebug = true; + return true; + }()); + + return isDebug; +} + +final blogApp = App(AppConfig( + name: 'Dart Blog', + environment: env('APP_ENV', isTestMode ? 'test' : 'development'), + isDebug: env('APP_DEBUG', true), + url: env('APP_URL', 'http://localhost'), + port: env('PORT', 80), + key: env('APP_KEY', UuidV4().generate()), +)); + +class App extends ApplicationFactory { + App(super.appConfig); + + @override + List get middlewares => [CoreMiddleware]; + + @override + Map> get middlewareGroups => { + 'web': [], + 'api:auth': [ApiAuthMiddleware], + }; + + @override + List get providers => ServiceProvider.defaultProviders + ..addAll([ + CoreProvider, + RouteServiceProvider, + DatabaseServiceProvider, + BlogServiceProvider, + ]); + + @override + FutureOr onApplicationException(Object error, Request request, Response response) { + if (error is RequestValidationError) { + return response.json(error.errorBody, statusCode: HttpStatus.badRequest); + } + + return super.onApplicationException(error, request, response); + } +} diff --git a/lib/src/services/article_service.dart b/lib/src/services/article_service.dart index 9c43367..a9d53c5 100644 --- a/lib/src/services/article_service.dart +++ b/lib/src/services/article_service.dart @@ -43,7 +43,7 @@ class ArticleService { try { final response = await http.get( Uri.parse('https://api.pexels.com/v1/search?query=$searchText&per_page=1'), - headers: {HttpHeaders.authorizationHeader: env('PEXELS_API_KEY', defaultValue: '')}, + headers: {HttpHeaders.authorizationHeader: env('PEXELS_API_KEY', '')}, ).timeout(const Duration(seconds: 2)); final result = await Isolate.run(() => jsonDecode(response.body)) as Map; return result['photos'][0]['src']['medium']; diff --git a/pubspec.lock b/pubspec.lock index c136b27..f245e9a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309 + sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 url: "https://pub.dev" source: hosted - version: "8.8.1" + version: "8.9.0" charcode: dependency: transitive description: @@ -153,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_completion: + dependency: transitive + description: + name: cli_completion + sha256: "1e87700c029c77041d836e57f9016b5c90d353151c43c2ca0c36deaadc05aa3a" + url: "https://pub.dev" + source: hosted + version: "0.4.0" cli_launcher: dependency: transitive description: @@ -434,6 +442,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + mason_logger: + dependency: transitive + description: + name: mason_logger + sha256: "3e68a3548e7b30d3c6066ac0f94c3392cc2c778d60a0f6909741896b19ac9ae4" + url: "https://pub.dev" + source: hosted + version: "0.2.11" matcher: dependency: transitive description: @@ -462,10 +478,10 @@ packages: dependency: "direct main" description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" mustache_template: dependency: transitive description: @@ -551,10 +567,10 @@ packages: dependency: transitive description: name: postgres - sha256: d384c0cce2c30f9b349a133812f976ca16fa916fd567882bb6a01741b194c22a + sha256: "4946cca91527b4f5fd85bd7b347c3a09108e69904f304de448c8b52bfdaa7fe6" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" process: dependency: transitive description: @@ -744,10 +760,10 @@ packages: dependency: transitive description: name: sqflite_common - sha256: "76db4d324c8cbb16ca5b60ad2f3d25cec953107c93ae65aafa480d3e6fb69f14" + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" url: "https://pub.dev" source: hosted - version: "2.5.2-1" + version: "2.5.3" sqflite_common_ffi: dependency: transitive description: @@ -824,10 +840,10 @@ packages: dependency: "direct dev" description: name: test - sha256: "694c108e13c6b35b15fcb0f8f03eddf8373f93b044c9497b5e81ce09f7381bda" + sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" url: "https://pub.dev" source: hosted - version: "1.25.1" + version: "1.25.2" test_api: dependency: transitive description: @@ -940,6 +956,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + url: "https://pub.dev" + source: hosted + version: "5.2.0" yaml: dependency: transitive description: @@ -959,18 +983,27 @@ packages: yaroo: dependency: "direct main" description: - path: yaroo + path: "packages/yaroo" ref: HEAD - resolved-ref: c64772e05ea14b4e4e987ac50e93a7ec673a54d8 + resolved-ref: cad334d5a29ef204f3f770614870780fe5ff7b39 url: "https://github.com/codekeyz/yaroo.git" source: git version: "0.0.1" + yaroo_cli: + dependency: "direct dev" + description: + path: "packages/yaroo_cli" + ref: HEAD + resolved-ref: cad334d5a29ef204f3f770614870780fe5ff7b39 + url: "https://github.com/codekeyz/yaroo.git" + source: git + version: "1.0.0" yaroorm: dependency: "direct main" description: - path: yaroorm + path: "packages/yaroorm" ref: HEAD - resolved-ref: c64772e05ea14b4e4e987ac50e93a7ec673a54d8 + resolved-ref: cad334d5a29ef204f3f770614870780fe5ff7b39 url: "https://github.com/codekeyz/yaroo.git" source: git version: "1.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 366dc0b..9aaba73 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,11 +17,11 @@ dependencies: yaroo: git: url: https://github.com/codekeyz/yaroo.git - path: yaroo + path: packages/yaroo yaroorm: git: url: https://github.com/codekeyz/yaroo.git - path: yaroorm + path: packages/yaroorm collection: ^1.18.0 logger: ^2.0.2+1 @@ -33,3 +33,7 @@ dev_dependencies: test: ^1.24.0 json_serializable: ^6.7.1 melos: ^3.2.0 + yaroo_cli: + git: + url: https://github.com/codekeyz/yaroo.git + path: packages/yaroo_cli diff --git a/test/backend_test.dart b/test/backend_test.dart index 5dfcff0..05e9133 100644 --- a/test/backend_test.dart +++ b/test/backend_test.dart @@ -1,22 +1,35 @@ import 'dart:convert'; import 'dart:io'; -import 'package:backend/app/app.dart'; -import 'package:test/test.dart'; +import 'package:backend/backend.dart'; +import 'package:yaroo/yaroo.dart'; import 'package:yaroorm/yaroorm.dart'; +import 'package:path/path.dart' as path; -import '../config/app.dart' as app; -import '../config/database.dart' as db; import 'backend_test.reflectable.dart'; -final server = App(app.config); +final ormConfig = YaroormConfig( + 'sqlite', + connections: [ + DatabaseConnection( + 'sqlite', + DatabaseDriverType.sqlite, + database: path.absolute('database', 'db.sqlite'), + ), + ], +); void main() { initializeReflectable(); - DB.init(db.config); + DB.init(ormConfig); - setUpAll(() => server.bootstrap(listen: false)); + late Spookie blogAppTester; + + setUpAll(() async { + await blogApp.bootstrap(listen: false); + blogAppTester = await blogApp.tester; + }); group('Backend API', () { const baseAPIPath = '/api'; @@ -27,7 +40,7 @@ void main() { final path = '$authPath/register'; test('should error on invalid body', () async { attemptRegister(Map body, {dynamic errors}) async { - return (await server.tester) + return blogAppTester .post(path, body) .expectStatus(HttpStatus.badRequest) .expectJsonBody({'location': 'body', 'errors': errors}).test(); @@ -72,8 +85,11 @@ void main() { test('should create user', () async { final newUserEmail = 'foo-${DateTime.now().millisecondsSinceEpoch}@bar.com'; - final apiResult = await (await server.tester) - .post(path, {'name': 'Foo User', 'email': newUserEmail, 'password': 'foo-bar-mee-moo'}).actual; + final apiResult = await blogAppTester.post(path, { + 'name': 'Foo User', + 'email': newUserEmail, + 'password': 'foo-bar-mee-moo', + }).actual; expect(apiResult.statusCode, HttpStatus.ok); expect(apiResult.headers[HttpHeaders.contentTypeHeader], 'application/json; charset=utf-8'); @@ -90,7 +106,7 @@ void main() { final randomUser = await DB.query().get(); expect(randomUser, isA()); - await (await server.tester) + await blogAppTester .post(path, {'email': randomUser!.email, 'name': 'Foo Bar', 'password': 'moooasdfmdf'}) .expectStatus(HttpStatus.badRequest) .expectJsonBody({ @@ -105,7 +121,7 @@ void main() { test('should error on invalid body', () async { attemptLogin(Map body, {dynamic errors}) async { - return (await server.tester) + return blogAppTester .post('$authPath/login', body) .expectStatus(HttpStatus.badRequest) .expectJsonBody({'location': 'body', 'errors': errors}).test(); @@ -125,7 +141,7 @@ void main() { final email = randomUser!.email; - await (await server.tester) + await blogAppTester .post(path, {'email': email, 'password': 'wap wap wap'}) .expectStatus(HttpStatus.unauthorized) .expectJsonBody({ @@ -133,7 +149,7 @@ void main() { }) .test(); - await (await server.tester) + await blogAppTester .post(path, {'email': 'holy@bar.com', 'password': 'wap wap wap'}) .expectStatus(HttpStatus.unauthorized) .expectJsonBody({ @@ -146,8 +162,10 @@ void main() { final randomUser = await DB.query().get(); expect(randomUser, isA()); - final baseTest = - (await server.tester).post(path, {'email': randomUser!.email, 'password': 'foo-bar-mee-moo'}); + final baseTest = blogAppTester.post(path, { + 'email': randomUser!.email, + 'password': 'foo-bar-mee-moo', + }); await baseTest .expectStatus(HttpStatus.ok) @@ -166,7 +184,7 @@ void main() { currentUser = await DB.query().get(); expect(currentUser, isA()); - final result = await (await server.tester).post('$baseAPIPath/auth/login', { + final result = await blogAppTester.post('$baseAPIPath/auth/login', { 'email': currentUser!.email, 'password': 'foo-bar-mee-moo', }).actual; @@ -181,26 +199,25 @@ void main() { final usersApiPath = '$baseAPIPath/users'; test('should reject if no cookie', () async { - await (await server.tester) + await blogAppTester .get(usersApiPath) .expectStatus(HttpStatus.unauthorized) .expectJsonBody({'error': 'Unauthorized'}).test(); }); test('should return user for /users/me ', () async { - await (await server.tester) + await blogAppTester .get('$usersApiPath/me', headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.ok) .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') .expectBodyCustom( - (body) => jsonDecode(body)['user'], - allOf( - contains('id'), - contains('email'), - contains('name'), - contains('createdAt'), - contains('updatedAt'), - )) + (body) => User.fromJson(jsonDecode(body)['user']), + isA().having( + (user) => [user.id, user.createdAt, user.updatedAt].every((e) => e != null), + 'user with properties', + isTrue, + ), + ) .test(); }); @@ -208,7 +225,7 @@ void main() { final randomUser = await DB.query().get(); expect(randomUser, isA()); - await (await server.tester) + await blogAppTester .get('$usersApiPath/${randomUser!.id!}') .expectStatus(HttpStatus.ok) .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8') @@ -222,8 +239,8 @@ void main() { group('create', () { test('should error on invalid body', () async { - attemptCreate(Map body, {dynamic errors}) async { - return (await server.tester) + Future attemptCreate(Map body, {dynamic errors}) async { + return blogAppTester .post(articleApiPath, body, headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.badRequest) .expectJsonBody({'location': 'body', 'errors': errors}) @@ -244,7 +261,7 @@ void main() { }); test('should create with image', () async { - final result = await (await server.tester).post(articleApiPath, { + final result = await blogAppTester.post(articleApiPath, { 'title': 'Santa Clause 🚀', 'description': 'Dart for backend is here', 'imageUrl': 'https://holy-dart.com/dart-logo-for-shares.png' @@ -258,13 +275,17 @@ void main() { expect(article.title, 'Santa Clause 🚀'); expect(article.description, 'Dart for backend is here'); expect(article.imageUrl, 'https://holy-dart.com/dart-logo-for-shares.png'); - expect(article.toJson(), allOf(contains('id'), contains('createdAt'), contains('updatedAt'))); + expect(article.id, isNotNull); + expect(article.createdAt, isNotNull); + expect(article.updatedAt, isNotNull); }); test('should use default image if none set', () async { - final result = await (await server.tester).post( - articleApiPath, {'title': 'Hurry 🚀', 'description': 'Welcome to the jungle'}, - headers: {HttpHeaders.cookieHeader: authCookie!}).actual; + final result = await blogAppTester.post( + articleApiPath, + {'title': 'Hurry 🚀', 'description': 'Welcome to the jungle'}, + headers: {HttpHeaders.cookieHeader: authCookie!}, + ).actual; expect(result.statusCode, HttpStatus.ok); final article = Article.fromJson(jsonDecode(result.body)['article']); @@ -272,14 +293,16 @@ void main() { expect(article.title, 'Hurry 🚀'); expect(article.description, 'Welcome to the jungle'); expect(article.imageUrl, 'https://dart.dev/assets/shared/dart-logo-for-shares.png'); - expect(article.toJson(), allOf(contains('id'), contains('createdAt'), contains('updatedAt'))); + expect(article.id, isNotNull); + expect(article.createdAt, isNotNull); + expect(article.updatedAt, isNotNull); }); }); group('update ', () { test('should error when invalid params', () async { // bad params - await (await server.tester) + await blogAppTester .put('$articleApiPath/some-random-id', headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.badRequest) .expectJsonBody({ @@ -289,7 +312,7 @@ void main() { .test(); // bad body - await (await server.tester) + await blogAppTester .put('$articleApiPath/234', body: {'name': 'foo'}, headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.badRequest) .expectJsonBody({ @@ -299,10 +322,12 @@ void main() { .test(); // no existing article - await (await server.tester) - .put('$articleApiPath/234', - body: {'title': 'Honey', 'description': 'Hold my beer lets talk'}, - headers: {HttpHeaders.cookieHeader: authCookie!}) + await blogAppTester + .put( + '$articleApiPath/234', + body: {'title': 'Honey', 'description': 'Hold my beer lets talk'}, + headers: {HttpHeaders.cookieHeader: authCookie!}, + ) .expectStatus(HttpStatus.notFound) .expectJsonBody({'error': 'Not found'}) .test(); @@ -315,9 +340,11 @@ void main() { expect(article!.title, isNot('Honey')); expect(article.description, isNot('Hold my beer lets talk')); - final result = await (await server.tester).put('$articleApiPath/${article.id}', - body: {'title': 'Honey', 'description': 'Hold my beer lets talk'}, - headers: {HttpHeaders.cookieHeader: authCookie!}).actual; + final result = await blogAppTester.put( + '$articleApiPath/${article.id}', + body: {'title': 'Honey', 'description': 'Hold my beer lets talk'}, + headers: {HttpHeaders.cookieHeader: authCookie!}, + ).actual; expect(result.statusCode, HttpStatus.ok); final updatedArticle = Article.fromJson(jsonDecode(result.body)['article']); @@ -330,7 +357,7 @@ void main() { group('delete', () { test('should error when invalid params', () async { // bad params - await (await server.tester) + await blogAppTester .delete('$articleApiPath/some-random-id', headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.badRequest) .expectJsonBody({ @@ -343,7 +370,7 @@ void main() { final article = await DB.query
().get(fakeId); expect(article, isNull); - await (await server.tester) + await blogAppTester .delete('$articleApiPath/$fakeId', headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.ok) .expectJsonBody({'message': 'Article deleted'}) @@ -354,7 +381,7 @@ void main() { final article = await DB.query
().whereEqual('ownerId', currentUser!.id!).findOne(); expect(article, isA
()); - await (await server.tester) + await blogAppTester .delete('$articleApiPath/${article!.id}', headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.ok) .expectJsonBody({'message': 'Article deleted'}) @@ -366,7 +393,7 @@ void main() { group('when get article by Id', () { test('should error when invalid articleId', () async { - await (await server.tester) + await blogAppTester .get('$articleApiPath/some-random-id', headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.badRequest) .expectJsonBody({ @@ -377,7 +404,7 @@ void main() { }); test('should error when article not exist', () async { - await (await server.tester) + await blogAppTester .get('$articleApiPath/2348', headers: {HttpHeaders.cookieHeader: authCookie!}) .expectStatus(HttpStatus.notFound) .expectJsonBody({'error': 'Not found'}) @@ -388,7 +415,7 @@ void main() { final article = await DB.query
().whereEqual('ownerId', currentUser!.id!).findOne(); expect(article, isA
()); - await (await server.tester) + await blogAppTester .get('$articleApiPath/${article!.id}') .expectStatus(HttpStatus.ok) .expectJsonBody({'article': article.toJson()}).test(); @@ -399,7 +426,7 @@ void main() { final articles = await DB.query
().all(); expect(articles, isNotEmpty); - await (await server.tester) + await blogAppTester .get('$baseAPIPath/articles') .expectStatus(HttpStatus.ok) .expectHeader(HttpHeaders.contentTypeHeader, 'application/json; charset=utf-8')