From d2828edbef5ca921b60a363888f953661262eaf2 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum <nweiz@google.com> Date: Mon, 13 Apr 2015 12:26:32 -0700 Subject: [PATCH] Change the URL-space exposed by the browser server. This is a step towards Dartium support. Rather than only serving the compiled JS and the HTML necessary to run it, the entire package is now served with the compiled JS and HTML wrappers overlayed on top. This will allow Dartium to load all necessary sources from the server, and incidentally also allows tests to access assets. See #60 Closes #27 R=kevmoo@google.com Review URL: https://codereview.chromium.org//1076803003 --- CHANGELOG.md | 5 + lib/pub_serve.dart | 12 +-- lib/src/runner/browser/server.dart | 151 +++++++++++++++++++-------- lib/src/runner/loader.dart | 13 ++- lib/src/util/one_off_handler.dart | 18 ++-- lib/src/util/path_handler.dart | 58 ++++++++++ lib/src/utils.dart | 63 +++++++++++ pubspec.yaml | 3 +- test/runner/browser/loader_test.dart | 6 +- test/runner/loader_test.dart | 5 +- test/runner/pub_serve_test.dart | 6 +- test/util/path_handler_test.dart | 77 ++++++++++++++ 12 files changed, 343 insertions(+), 74 deletions(-) create mode 100644 lib/src/util/path_handler.dart create mode 100644 test/util/path_handler_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index f0faab10..b37d10a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +### 0.12.0-beta.7 + +* Browser tests can now load assets by making HTTP requests to the corresponding + relative URLs. + ### 0.12.0-beta.6 * Add the ability to run multiple test suites concurrently. By default a number diff --git a/lib/pub_serve.dart b/lib/pub_serve.dart index f7cf3732..71fa1cff 100644 --- a/lib/pub_serve.dart +++ b/lib/pub_serve.dart @@ -19,16 +19,16 @@ class PubServeTransformer extends Transformer implements DeclaringTransformer { void declareOutputs(DeclaringTransform transform) { var id = transform.primaryId; - transform.declareOutput(id.changeExtension('.vm_test.dart')); - transform.declareOutput(id.changeExtension('.browser_test.dart')); - transform.declareOutput(id.changeExtension('.browser_test.html')); + transform.declareOutput(id.addExtension('.vm_test.dart')); + transform.declareOutput(id.addExtension('.browser_test.dart')); + transform.declareOutput(id.addExtension('.browser_test.html')); } void apply(Transform transform) { var id = transform.primaryInput.id; transform.addOutput( - new Asset.fromString(id.changeExtension('.vm_test.dart'), ''' + new Asset.fromString(id.addExtension('.vm_test.dart'), ''' import "package:test/src/runner/vm/isolate_listener.dart"; import "${p.url.basename(id.path)}" as test; @@ -39,7 +39,7 @@ void main(_, Map message) { } ''')); - var browserId = id.changeExtension('.browser_test.dart'); + var browserId = id.addExtension('.browser_test.dart'); transform.addOutput(new Asset.fromString(browserId, ''' import "package:test/src/runner/browser/iframe_listener.dart"; @@ -51,7 +51,7 @@ void main(_) { ''')); transform.addOutput( - new Asset.fromString(browserId.changeExtension('.html'), ''' + new Asset.fromString(id.addExtension('.browser_test.html'), ''' <!DOCTYPE html> <html> <head> diff --git a/lib/src/runner/browser/server.dart b/lib/src/runner/browser/server.dart index a98bcb08..1c2ee38e 100644 --- a/lib/src/runner/browser/server.dart +++ b/lib/src/runner/browser/server.dart @@ -18,6 +18,7 @@ import 'package:shelf_web_socket/shelf_web_socket.dart'; import '../../backend/suite.dart'; import '../../backend/test_platform.dart'; import '../../util/io.dart'; +import '../../util/path_handler.dart'; import '../../util/one_off_handler.dart'; import '../../utils.dart'; import '../load_exception.dart'; @@ -33,6 +34,9 @@ import 'firefox.dart'; class BrowserServer { /// Starts the server. /// + /// [root] is the root directory that the server should serve. It defaults to + /// the working directory. + /// /// If [packageRoot] is passed, it's used for all package imports when /// compiling tests to JS. Otherwise, the package root is inferred from the /// location of the source file. @@ -41,25 +45,35 @@ class BrowserServer { /// instance at that URL rather than from the filesystem. /// /// If [color] is true, console colors will be used when compiling Dart. - static Future<BrowserServer> start({String packageRoot, Uri pubServeUrl, - bool color: false}) { - var server = new BrowserServer._(packageRoot, pubServeUrl, color); + static Future<BrowserServer> start({String root, String packageRoot, + Uri pubServeUrl, bool color: false}) { + var server = new BrowserServer._(root, packageRoot, pubServeUrl, color); return server._load().then((_) => server); } /// The underlying HTTP server. HttpServer _server; + /// A randomly-generated secret. + /// + /// This is used to ensure that other users on the same system can't snoop + /// on data being served through this server. + final _secret = randomBase64(24, urlSafe: true); + /// The URL for this server. - Uri get url => baseUrlForAddress(_server.address, _server.port); + Uri get url => baseUrlForAddress(_server.address, _server.port) + .resolve(_secret + "/"); - /// a [OneOffHandler] for servicing WebSocket connections for + /// A [OneOffHandler] for servicing WebSocket connections for /// [BrowserManager]s. /// /// This is one-off because each [BrowserManager] can only connect to a single /// WebSocket, final _webSocketHandler = new OneOffHandler(); + /// A [PathHandler] used to serve compiled JS. + final _jsHandler = new PathHandler(); + /// The [CompilerPool] managing active instances of `dart2js`. /// /// This is `null` if tests are loaded from `pub serve`. @@ -68,6 +82,9 @@ class BrowserServer { /// The temporary directory in which compiled JS is emitted. final String _compiledDir; + /// The root directory served statically by this server. + final String _root; + /// The package root which is passed to `dart2js`. final String _packageRoot; @@ -109,8 +126,9 @@ class BrowserServer { /// per run, rather than one per browser per run. final _compileFutures = new Map<String, Future>(); - BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color) - : _pubServeUrl = pubServeUrl, + BrowserServer._(String root, this._packageRoot, Uri pubServeUrl, bool color) + : _root = root == null ? p.current : root, + _pubServeUrl = pubServeUrl, _compiledDir = pubServeUrl == null ? createTempDir() : null, _http = pubServeUrl == null ? null : new HttpClient(), _compilers = new CompilerPool(color: color); @@ -121,19 +139,70 @@ class BrowserServer { .add(_webSocketHandler.handler); if (_pubServeUrl == null) { - var staticPath = p.join(libDir(packageRoot: _packageRoot), - 'src/runner/browser/static'); cascade = cascade - .add(createStaticHandler(staticPath, defaultDocument: 'index.html')) - .add(createStaticHandler(_compiledDir, - defaultDocument: 'index.html')); + .add(_createPackagesHandler()) + .add(_jsHandler.handler) + .add(_wrapperHandler) + .add(createStaticHandler(_root)); } - return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { + var pipeline = new shelf.Pipeline() + .addMiddleware(nestingMiddleware(_secret)) + .addHandler(cascade.handler); + + return shelf_io.serve(pipeline, 'localhost', 0).then((server) { _server = server; }); } + /// Returns a handler that serves the contents of the "packages/" directory + /// for any URL that contains "packages/". + /// + /// This is a factory so it can wrap a static handler. + shelf.Handler _createPackagesHandler() { + var packageRoot = _packageRoot == null + ? p.join(_root, 'packages') + : _packageRoot; + var staticHandler = + createStaticHandler(packageRoot, serveFilesOutsidePath: true); + + return (request) { + var segments = p.url.split(shelfUrl(request).path); + + for (var i = 0; i < segments.length; i++) { + if (segments[i] != "packages") continue; + return staticHandler( + shelfChange(request, path: p.url.joinAll(segments.take(i + 1)))); + } + + return new shelf.Response.notFound("Not found."); + }; + } + + /// A handler that serves wrapper HTML to bootstrap tests. + shelf.Response _wrapperHandler(shelf.Request request) { + var path = p.fromUri(shelfUrl(request)); + var withoutExtensions = p.withoutExtension(p.withoutExtension(path)); + var base = p.basename(withoutExtensions); + + if (path.endsWith(".browser_test.html")) { + // TODO(nweiz): support user-authored HTML files. + return new shelf.Response.ok(''' +<!DOCTYPE html> +<html> +<head> + <title>${HTML_ESCAPE.convert(base)}.dart Test</title> + <script type="application/javascript" + src="${HTML_ESCAPE.convert(base)}.browser_test.dart.js"> + </script> +</head> +</html> +''', headers: {'Content-Type': 'text/html'}); + } + + return new shelf.Response.notFound('Not found.'); + } + /// Loads the test suite at [path] on the browser [browser]. /// /// This will start a browser to load the suite if one isn't already running. @@ -145,22 +214,18 @@ class BrowserServer { return new Future.sync(() { if (_pubServeUrl != null) { - var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) + + var suitePrefix = p.relative(path, from: p.join(_root, 'test')) + '.browser_test'; var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js'); - return _pubServeSuite(path, jsUrl) - .then((_) => _pubServeUrl.resolve('$suitePrefix.html')); - } else { - return _compileSuite(path).then((dir) { - if (_closed) return null; - - // Add a trailing slash because at least on Chrome, the iframe's - // window.location.href will do so automatically, and if that differs - // from the original URL communication will fail. - return url.resolve( - "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/"); - }); + return _pubServeSuite(path, jsUrl).then((_) => + _pubServeUrl.resolve('$suitePrefix.html')); } + + return _compileSuite(path).then((_) { + if (_closed) return null; + return url.resolveUri( + p.toUri(p.relative(path, from: _root) + ".browser_test.html")); + }); }).then((suiteUrl) { if (_closed) return null; @@ -210,27 +275,24 @@ class BrowserServer { /// Compile the test suite at [dartPath] to JavaScript. /// - /// Returns a [Future] that completes to the path to the JavaScript. - Future<String> _compileSuite(String dartPath) { + /// Once the suite has been compiled, it's added to [_jsHandler] so it can be + /// served. + Future _compileSuite(String dartPath) { return _compileFutures.putIfAbsent(dartPath, () { var dir = new Directory(_compiledDir).createTempSync('test_').path; var jsPath = p.join(dir, p.basename(dartPath) + ".js"); + return _compilers.compile(dartPath, jsPath, packageRoot: packageRootFor(dartPath, _packageRoot)) .then((_) { - if (_closed) return null; + if (_closed) return; - // TODO(nweiz): support user-authored HTML files. - new File(p.join(dir, "index.html")).writeAsStringSync(''' -<!DOCTYPE html> -<html> -<head> - <title>${HTML_ESCAPE.convert(dartPath)} Test</title> - <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script> -</head> -</html> -'''); - return dir; + _jsHandler.add( + p.relative(dartPath, from: _root) + '.browser_test.dart.js', + (request) { + return new shelf.Response.ok(new File(jsPath).readAsStringSync(), + headers: {'Content-Type': 'application/javascript'}); + }); }); }); } @@ -248,13 +310,10 @@ class BrowserServer { completer.complete(new BrowserManager(webSocket)); })); - var webSocketUrl = url.replace(scheme: 'ws', path: '/$path'); + var webSocketUrl = url.replace(scheme: 'ws').resolve(path); - var hostUrl = url; - if (_pubServeUrl != null) { - hostUrl = _pubServeUrl.resolve( - '/packages/test/src/runner/browser/static/'); - } + var hostUrl = (_pubServeUrl == null ? url : _pubServeUrl) + .resolve('packages/test/src/runner/browser/static/index.html'); var browser = _newBrowser(hostUrl.replace(queryParameters: { 'managerUrl': webSocketUrl.toString() diff --git a/lib/src/runner/loader.dart b/lib/src/runner/loader.dart index afff40a5..475e10d5 100644 --- a/lib/src/runner/loader.dart +++ b/lib/src/runner/loader.dart @@ -32,6 +32,9 @@ class Loader { /// Whether to enable colors for Dart compilation. final bool _color; + /// The root directory that will be served for browser tests. + final String _root; + /// The package root to use for loading tests, or `null` to use the automatic /// root. final String _packageRoot; @@ -51,6 +54,7 @@ class Loader { if (_browserServerCompleter == null) { _browserServerCompleter = new Completer(); BrowserServer.start( + root: _root, packageRoot: _packageRoot, pubServeUrl: _pubServeUrl, color: _color) @@ -63,6 +67,9 @@ class Loader { /// Creates a new loader. /// + /// [root] is the root directory that will be served for browser tests. It + /// defaults to the working directory. + /// /// If [packageRoot] is passed, it's used as the package root for all loaded /// tests. Otherwise, the `packages/` directories next to the test entrypoints /// will be used. @@ -71,10 +78,11 @@ class Loader { /// instance at that URL rather than from the filesystem. /// /// If [color] is true, console colors will be used when compiling Dart. - Loader(Iterable<TestPlatform> platforms, {String packageRoot, + Loader(Iterable<TestPlatform> platforms, {String root, String packageRoot, Uri pubServeUrl, bool color: false}) : _platforms = platforms.toList(), _pubServeUrl = pubServeUrl, + _root = root == null ? p.current : root, _packageRoot = packageRoot, _color = color; @@ -154,8 +162,7 @@ class Loader { return new Future.sync(() { if (_pubServeUrl != null) { var url = _pubServeUrl.resolve( - p.withoutExtension(p.relative(path, from: 'test')) + - '.vm_test.dart'); + p.relative(path, from: 'test') + '.vm_test.dart'); return Isolate.spawnUri(url, [], {'reply': receivePort.sendPort}) .then((isolate) => new IsolateWrapper(isolate, () {})) .catchError((error, stackTrace) { diff --git a/lib/src/util/one_off_handler.dart b/lib/src/util/one_off_handler.dart index 069d6818..442a7648 100644 --- a/lib/src/util/one_off_handler.dart +++ b/lib/src/util/one_off_handler.dart @@ -2,11 +2,13 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -library test.one_off_handler; +library test.util.one_off_handler; import 'package:path/path.dart' as p; import 'package:shelf/shelf.dart' as shelf; +import '../utils.dart'; + /// A Shelf handler that provides support for one-time handlers. /// /// This is useful for handlers that only expect to be hit once before becoming @@ -36,18 +38,12 @@ class OneOffHandler { /// Dispatches [request] to the appropriate handler. _onRequest(shelf.Request request) { - var components = p.url.split(request.url.path); - - // For shelf < 0.6.0, the first component of the path is always "/". We can - // safely skip it. - if (components.isNotEmpty && components.first == "/") { - components.removeAt(0); - } - + var components = p.url.split(shelfUrl(request).path); if (components.isEmpty) return new shelf.Response.notFound(null); - var handler = _handlers.remove(components.removeAt(0)); + var path = components.removeAt(0); + var handler = _handlers.remove(path); if (handler == null) return new shelf.Response.notFound(null); - return handler(request); + return handler(shelfChange(request, path: path)); } } diff --git a/lib/src/util/path_handler.dart b/lib/src/util/path_handler.dart new file mode 100644 index 00000000..45ce45a2 --- /dev/null +++ b/lib/src/util/path_handler.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library test.util.path_handler; + +import 'package:path/path.dart' as p; +import 'package:shelf/shelf.dart' as shelf; + +import '../utils.dart'; + +/// A handler that routes to sub-handlers based on exact path prefixes. +class PathHandler { + /// A trie of path components to handlers. + final _paths = new _Node(); + + /// The shelf handler. + shelf.Handler get handler => _onRequest; + + PathHandler(); + + /// Routes requests at or under [path] to [handler]. + /// + /// If [path] is a parent or child directory of another path in this handler, + /// the longest matching prefix wins. + void add(String path, shelf.Handler handler) { + var node = _paths; + for (var component in p.url.split(path)) { + node = node.children.putIfAbsent(component, () => new _Node()); + } + node.handler = handler; + } + + _onRequest(shelf.Request request) { + var handler; + var handlerIndex; + var node = _paths; + var components = p.url.split(shelfUrl(request).path); + for (var i = 0; i < components.length; i++ ) { + node = node.children[components[i]]; + if (node == null) break; + if (node.handler == null) continue; + handler = node.handler; + handlerIndex = i; + } + + if (handler == null) return new shelf.Response.notFound("Not found."); + + return handler(shelfChange(request, + path: p.joinAll(components.take(handlerIndex + 1)))); + } +} + +/// A trie node. +class _Node { + shelf.Handler handler; + final children = new Map<String, _Node>(); +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 7d1094ed..37f2e663 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -5,11 +5,15 @@ library test.utils; import 'dart:async'; +import 'dart:math' as math; +import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as p; +import 'package:shelf/shelf.dart' as shelf; import 'package:stack_trace/stack_trace.dart'; import 'backend/operating_system.dart'; +import 'util/path_handler.dart'; /// A typedef for a possibly-asynchronous function. /// @@ -179,3 +183,62 @@ Stream mergeStreams(Iterable<Stream> streamIter) { return controller.stream; } + +/// Returns a random base64 string containing [bytes] bytes of data. +/// +/// [seed] is passed to [math.Random]; [urlSafe] and [addLineSeparator] are +/// passed to [CryptoUtils.bytesToBase64]. +String randomBase64(int bytes, {int seed, bool urlSafe: false, + bool addLineSeparator: false}) { + var random = new math.Random(seed); + var data = []; + for (var i = 0; i < bytes; i++) { + data.add(random.nextInt(256)); + } + return CryptoUtils.bytesToBase64(data, + urlSafe: urlSafe, addLineSeparator: addLineSeparator); +} + +// TODO(nweiz): Remove this and [shelfChange] once Shelf 0.6.0 has been out for +// six months or so. +/// Returns `request.url` in a cross-version way. +/// +/// This follows the semantics of Shelf 0.6.x, even when using Shelf 0.5.x: the +/// returned URL never starts with "/". +Uri shelfUrl(shelf.Request request) { + var url = request.url; + if (!url.path.startsWith("/")) return url; + return url.replace(path: url.path.replaceFirst("/", "")); +} + +/// Like [shelf.Request.change], but cross-version. +/// +/// This follows the semantics of Shelf 0.6.x, even when using Shelf 0.5.x. +shelf.Request shelfChange(shelf.Request typedRequest, {String path}) { + // Explicitly make the request dynamic since we're calling methods here that + // aren't defined in all support Shelf versions, and we don't want the + // analyzer to complain. + var request = typedRequest as dynamic; + + try { + return request.change(path: path); + } on NoSuchMethodError catch (_) { + var newScriptName = p.url.join(request.scriptName, path); + if (request.scriptName.isEmpty) newScriptName = "/" + newScriptName; + + var newUrlPath = p.url.relative(request.url.path.replaceFirst("/", ""), + from: path); + newUrlPath = newUrlPath == "." ? "" : "/" + newUrlPath; + + return request.change( + scriptName: newScriptName, url: request.url.replace(path: newUrlPath)); + } +} + +/// Returns middleware that nests all requests beneath the URL prefix [beneath]. +shelf.Middleware nestingMiddleware(String beneath) { + return (handler) { + var pathHandler = new PathHandler()..add(beneath, handler); + return pathHandler.handler; + }; +} diff --git a/pubspec.yaml b/pubspec.yaml index f28b7cb5..3aedd585 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: test -version: 0.12.0-beta.6 +version: 0.12.0-dev author: Dart Team <misc@dartlang.org> description: A library for writing dart unit tests. homepage: https://github.com/dart-lang/test @@ -9,6 +9,7 @@ dependencies: analyzer: '>=0.23.0 <0.25.0' args: '>=0.12.1 <0.14.0' barback: '>=0.14.0 <0.16.0' + crypto: '^0.9.0' http_parser: '^0.0.2' pool: '^1.0.0' pub_semver: '^1.0.0' diff --git a/test/runner/browser/loader_test.dart b/test/runner/browser/loader_test.dart index a3e93c5f..322de04c 100644 --- a/test/runner/browser/loader_test.dart +++ b/test/runner/browser/loader_test.dart @@ -33,9 +33,10 @@ void main() { void main() { setUp(() { + _sandbox = createTempDir(); _loader = new Loader([TestPlatform.chrome], + root: _sandbox, packageRoot: p.join(packageDir, 'packages')); - _sandbox = createTempDir(); /// TODO(nweiz): Use scheduled_test for this once it's compatible with this /// version of test. new File(p.join(_sandbox, 'a_test.dart')).writeAsStringSync(_tests); @@ -88,7 +89,7 @@ void main() { }); test("throws a nice error if the package root doesn't exist", () { - var loader = new Loader([TestPlatform.chrome]); + var loader = new Loader([TestPlatform.chrome], root: _sandbox); expect( loader.loadFile(p.join(_sandbox, 'a_test.dart')).first .whenComplete(loader.close), @@ -98,6 +99,7 @@ void main() { test("loads a suite both in the browser and the VM", () { var loader = new Loader([TestPlatform.vm, TestPlatform.chrome], + root: _sandbox, packageRoot: p.join(packageDir, 'packages')); var path = p.join(_sandbox, 'a_test.dart'); return loader.loadFile(path).toList().then((suites) { diff --git a/test/runner/loader_test.dart b/test/runner/loader_test.dart index f5e81608..8474cbed 100644 --- a/test/runner/loader_test.dart +++ b/test/runner/loader_test.dart @@ -33,9 +33,10 @@ void main() { void main() { setUp(() { + _sandbox = createTempDir(); _loader = new Loader([TestPlatform.vm], + root: _sandbox, packageRoot: p.join(packageDir, 'packages')); - _sandbox = createTempDir(); }); tearDown(() { @@ -87,7 +88,7 @@ void main() { }); test("throws a nice error if the package root doesn't exist", () { - var loader = new Loader([TestPlatform.vm]); + var loader = new Loader([TestPlatform.vm], root: _sandbox); expect( loader.loadFile(p.join(_sandbox, 'a_test.dart')).first .whenComplete(loader.close), diff --git a/test/runner/pub_serve_test.dart b/test/runner/pub_serve_test.dart index 6304b8cf..d15370f5 100644 --- a/test/runner/pub_serve_test.dart +++ b/test/runner/pub_serve_test.dart @@ -223,7 +223,7 @@ transformers: contains('-1: load error'), contains(''' Failed to load "test/my_test.dart": - Error getting http://localhost:54321/my_test.vm_test.dart: Connection refused + Error getting http://localhost:54321/my_test.dart.vm_test.dart: Connection refused Make sure "pub serve" is running.''') ])); expect(result.exitCode, equals(1)); @@ -235,8 +235,8 @@ transformers: expect(result.stdout, allOf([ contains('-1: load error'), contains('Failed to load "test/my_test.dart":'), - contains('Error getting http://localhost:54321/my_test.browser_test.dart' - '.js: Connection refused (errno '), + contains('Error getting http://localhost:54321/my_test.dart.browser_test' + '.dart.js: Connection refused (errno '), contains('Make sure "pub serve" is running.') ])); expect(result.exitCode, equals(1)); diff --git a/test/util/path_handler_test.dart b/test/util/path_handler_test.dart new file mode 100644 index 00000000..d6ea38f1 --- /dev/null +++ b/test/util/path_handler_test.dart @@ -0,0 +1,77 @@ +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:shelf/shelf.dart' as shelf; +import 'package:test/src/util/path_handler.dart'; +import 'package:test/test.dart'; + +void main() { + var handler; + setUp(() => handler = new PathHandler()); + + _handle(request) => new Future.sync(() => handler.handler(request)); + + test("returns a 404 for a root URL", () { + var request = new shelf.Request("GET", Uri.parse("http://localhost/")); + return _handle(request).then((response) { + expect(response.statusCode, equals(404)); + }); + }); + + test("returns a 404 for an unregistered URL", () { + var request = new shelf.Request("GET", Uri.parse("http://localhost/foo")); + return _handle(request).then((response) { + expect(response.statusCode, equals(404)); + }); + }); + + test("runs a handler for an exact URL", () { + var request = new shelf.Request("GET", Uri.parse("http://localhost/foo")); + handler.add("foo", expectAsync((request) { + expect(request.handlerPath, equals('/foo')); + expect(request.url.path, isEmpty); + return new shelf.Response.ok("good job!"); + })); + + return _handle(request).then((response) { + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals("good job!"))); + }); + }); + + test("runs a handler for a suffix", () { + var request = new shelf.Request( + "GET", Uri.parse("http://localhost/foo/bar")); + handler.add("foo", expectAsync((request) { + expect(request.handlerPath, equals('/foo/')); + expect(request.url.path, 'bar'); + return new shelf.Response.ok("good job!"); + })); + + return _handle(request).then((response) { + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals("good job!"))); + }); + }); + + test("runs the longest matching handler", () { + var request = new shelf.Request( + "GET", Uri.parse("http://localhost/foo/bar/baz")); + + handler.add("foo", expectAsync((_) {}, count: 0)); + handler.add("foo/bar", expectAsync((request) { + expect(request.handlerPath, equals('/foo/bar/')); + expect(request.url.path, 'baz'); + return new shelf.Response.ok("good job!"); + })); + handler.add("foo/bar/baz/bang", expectAsync((_) {}, count: 0)); + + return _handle(request).then((response) { + expect(response.statusCode, equals(200)); + expect(response.readAsString(), completion(equals("good job!"))); + }); + }); +} -- GitLab