Skip to content

Commit

Permalink
fix(dart_frog): Pipeline does not maintain RequestContext (felang…
Browse files Browse the repository at this point in the history
  • Loading branch information
felangel committed Apr 13, 2023
1 parent fca92f8 commit 0ee1422
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 84 deletions.
21 changes: 9 additions & 12 deletions examples/counter/test/routes/_middleware_test.dart
Expand Up @@ -9,25 +9,22 @@ class _MockRequestContext extends Mock implements RequestContext {}
void main() {
group('middleware', () {
test('provides incremented count', () async {
int? count;
final handler = middleware(
(context) {
count = context.read<int>();
return Response(body: '');
},
);
final handler = middleware((context) => Response());
final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();

when(() => context.request).thenReturn(request);
when(() => context.provide<int>(any())).thenReturn(context);

await handler(context);
expect(count, equals(1));

await handler(context);
expect(count, equals(2));
final create = verify(() => context.provide<int>(captureAny()))
.captured
.single as int Function();

await handler(context);
expect(count, equals(3));
expect(create(), equals(1));
expect(create(), equals(2));
expect(create(), equals(3));
});
});
}
17 changes: 9 additions & 8 deletions examples/kitchen_sink/test/routes/_middleware_test.dart
Expand Up @@ -9,19 +9,20 @@ class _MockRequestContext extends Mock implements RequestContext {}
void main() {
group('middleware', () {
test('provides greeting', () async {
String? greeting;
final handler = middleware(
(context) {
greeting = context.read<String>();
return Response(body: '');
},
);
final handler = middleware((_) => Response());
final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();

when(() => context.request).thenReturn(request);
when(() => context.provide<String>(any())).thenReturn(context);

await handler(context);
expect(greeting, equals('Hello'));

final create = verify(() => context.provide<String>(captureAny()))
.captured
.single as String Function();

expect(create(), equals('Hello'));
});
});
}
16 changes: 8 additions & 8 deletions examples/web_socket_counter/test/routes/_middleware_test.dart
Expand Up @@ -10,19 +10,19 @@ class _MockRequestContext extends Mock implements RequestContext {}
void main() {
group('middleware', () {
test('provides a CounterCubit instance.', () async {
CounterCubit? cubit;
final handler = middleware(
(context) {
cubit = context.read<CounterCubit>();
return Response();
},
);
final handler = middleware((_) => Response());
final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();

when(() => context.request).thenReturn(request);
when(() => context.provide<CounterCubit>(any())).thenReturn(context);

await handler(context);
expect(cubit, isNotNull);

final create = verify(() => context.provide<CounterCubit>(captureAny()))
.captured
.single as CounterCubit Function();
expect(create(), isA<CounterCubit>());
});
});
}
44 changes: 14 additions & 30 deletions packages/dart_frog/lib/src/pipeline.dart
Expand Up @@ -6,44 +6,28 @@ part of '_internal.dart';
/// {@endtemplate}
class Pipeline {
/// {@macro pipeline}
const Pipeline() : this._(const shelf.Pipeline());

const Pipeline._(this._pipeline);

final shelf.Pipeline _pipeline;
const Pipeline();

/// Returns a new [Pipeline] with [middleware] added to the existing set of
/// [Middleware].
///
/// [middleware] will be the last [Middleware] to process a request and
/// the first to process a response.
Pipeline addMiddleware(Middleware middleware) {
return Pipeline._(
_pipeline.addMiddleware((innerHandler) {
return (request) async {
final response = await middleware(
(context) async {
final response = await innerHandler(context.request._request);
return Response._(response);
},
)(RequestContext._(request));
return response._response;
};
}),
);
}
Pipeline addMiddleware(Middleware middleware) =>
_Pipeline(middleware, addHandler);

/// Returns a new [Handler] with [handler] as the final processor of a
/// [Request] if all of the middleware in the pipeline have passed the request
/// through.
Handler addHandler(Handler handler) {
return (context) async {
final response = await _pipeline.addHandler((request) async {
final context = RequestContext._(request);
final response = await handler(context);
return response._response;
})(context.request._request);
return Response._(response);
};
}
Handler addHandler(Handler handler) => handler;
}

class _Pipeline extends Pipeline {
_Pipeline(this._middleware, this._parent);

final Middleware _middleware;
final Middleware _parent;

@override
Handler addHandler(Handler handler) => _parent(_middleware(handler));
}
38 changes: 30 additions & 8 deletions packages/dart_frog/test/src/middleware_test.dart
@@ -1,6 +1,7 @@
import 'dart:io';

import 'package:dart_frog/dart_frog.dart';
import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -31,16 +32,14 @@ void main() {
final handler =
const Pipeline().addMiddleware(middleware).addHandler(onRequest);

final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();
when(() => context.request).thenReturn(request);
final response = await handler(context);
final server = await serve(handler, 'localhost', 3020);
final client = http.Client();
final response = await client.get(Uri.parse('http://localhost:3020/'));

await expectLater(response.statusCode, equals(HttpStatus.ok));
await expectLater(
await response.body(),
equals('$stringValue $intValue'),
);
await expectLater(response.body, equals('$stringValue $intValue'));

await server.close();
});

test('middleware can be used to read the request body', () async {
Expand Down Expand Up @@ -119,4 +118,27 @@ void main() {
expect(response.statusCode, equals(HttpStatus.ok));
expect(response.body(), completion(equals(body)));
});

test('chaining middleware retains request context', () async {
const value = 'test-value';
Middleware noop() => (handler) => (context) => handler(context);

Future<Response> onRequest(RequestContext context) async {
final value = context.read<String>();
return Response(body: value);
}

final handler =
const Pipeline().addMiddleware(noop()).addHandler(onRequest);
final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();

when(() => context.read<String>()).thenReturn(value);
when(() => context.request).thenReturn(request);

final response = await handler(context);

expect(response.statusCode, equals(HttpStatus.ok));
expect(response.body(), completion(equals(value)));
});
}
44 changes: 26 additions & 18 deletions packages/dart_frog/test/src/provider_test.dart
@@ -1,11 +1,9 @@
import 'dart:io';

import 'package:dart_frog/dart_frog.dart';
import 'package:mocktail/mocktail.dart';
import 'package:http/http.dart' as http;
import 'package:test/test.dart';

class _MockRequestContext extends Mock implements RequestContext {}

void main() {
test('values can be provided and read via middleware', () async {
const value = '__test_value__';
Expand All @@ -21,13 +19,14 @@ void main() {
final handler =
const Pipeline().addMiddleware(middleware).addHandler(onRequest);

final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();
when(() => context.request).thenReturn(request);
final response = await handler(context);
final server = await serve(handler, 'localhost', 3010);
final client = http.Client();
final response = await client.get(Uri.parse('http://localhost:3010/'));

await expectLater(response.statusCode, equals(HttpStatus.ok));
await expectLater(await response.body(), equals(value));
await expectLater(response.body, equals(value));

await server.close();
});

test('descendant providers can access provided values', () async {
Expand All @@ -46,29 +45,38 @@ void main() {
final handler =
const Pipeline().addMiddleware(middleware).addHandler(onRequest);

final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();
when(() => context.request).thenReturn(request);
final response = await handler(context);
final server = await serve(handler, 'localhost', 3011);
final client = http.Client();
final response = await client.get(Uri.parse('http://localhost:3011/'));

await expectLater(response.statusCode, equals(HttpStatus.ok));
await expectLater(await response.body(), equals(url));
await expectLater(response.body, equals(url));

await server.close();
});

test('A StateError is thrown when reading an un-provided value', () async {
Object? exception;
Response onRequest(RequestContext context) {
context.read<Uri>();
try {
context.read<Uri>();
} catch (e) {
exception = e;
}
return Response();
}

final handler = const Pipeline()
.addMiddleware((handler) => handler)
.addHandler(onRequest);

final request = Request.get(Uri.parse('http://localhost/'));
final context = _MockRequestContext();
when(() => context.request).thenReturn(request);
final server = await serve(handler, 'localhost', 3012);
final client = http.Client();
final response = await client.get(Uri.parse('http://localhost:3012/'));

await expectLater(response.statusCode, equals(HttpStatus.ok));
expect(exception, isA<StateError>());

await expectLater(() => handler(context), throwsStateError);
await server.close();
});
}

0 comments on commit 0ee1422

Please sign in to comment.