[tests] Initial set of tests for the doc_checker lib code.
Change-Id: I94bea2f43ca1424201412f0a3c50f07fe30ca468
diff --git a/packages/tools/BUILD.gn b/packages/tools/BUILD.gn
index b513b5a..27ac4b0 100644
--- a/packages/tools/BUILD.gn
+++ b/packages/tools/BUILD.gn
@@ -28,5 +28,6 @@
testonly = true
public_deps = [
"//topaz/tools/doc_checker(//build/toolchain:host_x64)",
+ "//topaz/tools/doc_checker:tests(//build/toolchain:host_x64)",
]
}
diff --git a/tools/doc_checker/BUILD.gn b/tools/doc_checker/BUILD.gn
index 30c3713..71b75fe 100644
--- a/tools/doc_checker/BUILD.gn
+++ b/tools/doc_checker/BUILD.gn
@@ -3,6 +3,7 @@
# found in the LICENSE file.
import("//build/dart/dart_tool.gni")
+import("//build/dart/test.gni")
dart_tool("doc_checker") {
package_name = "doc_checker"
@@ -23,3 +24,25 @@
"//third_party/dart-pkg/pub/path",
]
}
+
+dart_test("doc_checker_tests") {
+ sources = [
+ "graph_test.dart",
+ "link_scraper_test.dart",
+ "link_verifier_test.dart",
+ ]
+
+ deps = [
+ ":doc_checker_dart_library",
+ "//third_party/dart-pkg/pub/meta",
+ "//third_party/dart-pkg/pub/test",
+ ]
+}
+
+group("tests") {
+ testonly = true
+
+ deps = [
+ ":doc_checker_tests($host_toolchain)",
+ ]
+}
diff --git a/tools/doc_checker/lib/graph.dart b/tools/doc_checker/lib/graph.dart
index 9936c11..67a8bff 100644
--- a/tools/doc_checker/lib/graph.dart
+++ b/tools/doc_checker/lib/graph.dart
@@ -13,6 +13,8 @@
int _nextId = 0;
Node _root;
+ int get nodeCount => _nodes.length;
+
/// Returns or creates a node with the given [label].
Node getNode(String label) =>
_nodes.putIfAbsent(label, () => Node._internal(label, _nextId++));
diff --git a/tools/doc_checker/lib/link_scraper.dart b/tools/doc_checker/lib/link_scraper.dart
index c5150dd..c491a50 100644
--- a/tools/doc_checker/lib/link_scraper.dart
+++ b/tools/doc_checker/lib/link_scraper.dart
@@ -5,13 +5,19 @@
import 'dart:io';
import 'package:markdown/markdown.dart';
+import 'package:meta/meta.dart';
/// Scrapes links in a markdown document.
class LinkScraper {
/// Extracts links from the given [file].
Iterable<String> scrape(String file) {
- final List<Node> nodes =
- Document().parseLines(File(file).readAsLinesSync());
+ return scrapeLines(File(file).readAsLinesSync());
+ }
+
+ /// Extracts links from the given list of [lines].
+ @visibleForTesting
+ Iterable<String> scrapeLines(List<String> lines) {
+ final List<Node> nodes = Document().parseLines(lines);
final _Visitor visitor = _Visitor();
for (Node node in nodes) {
node.accept(visitor);
diff --git a/tools/doc_checker/lib/link_verifier.dart b/tools/doc_checker/lib/link_verifier.dart
index 91a84f0..da341a0 100644
--- a/tools/doc_checker/lib/link_verifier.dart
+++ b/tools/doc_checker/lib/link_verifier.dart
@@ -6,6 +6,7 @@
import 'dart:io';
import 'package:http/http.dart' as http;
+import 'package:meta/meta.dart';
class Link<P> {
final Uri uri;
@@ -28,28 +29,31 @@
urisByDomain.putIfAbsent(link.uri.authority, () => []).add(link);
}
await Future.wait(urisByDomain.keys.map((String domain) =>
- _LinkVerifier(urisByDomain[domain]).verify(callback)));
+ LinkVerifier(urisByDomain[domain], http.Client()).verify(callback)));
return null;
}
-class _LinkVerifier<P> {
+@visibleForTesting
+class LinkVerifier<P> {
final List<Link<P>> links;
+ final http.Client client;
- _LinkVerifier(this.links);
+ LinkVerifier(this.links, this.client);
Future<Null> verify(OnElementVerified<P> callback) async {
for (Link<P> link in links) {
- callback(link, await _verifyLink(link));
+ callback(link, await verifyLink(link));
}
return null;
}
- Future<bool> _verifyLink(Link<P> link) async {
+ @visibleForTesting
+ Future<bool> verifyLink(Link<P> link) async {
try {
for (int i = 0; i < 3; i++) {
- final http.Response response = await http.get(link.uri, headers: {
- HttpHeaders.acceptHeader:
- 'text/html,application/xhtml+xml,application/xml,',
+ final http.Response response = await client.get(link.uri, headers: {
+ HttpHeaders.acceptHeader:
+ 'text/html,application/xhtml+xml,application/xml,',
});
final int code = response.statusCode;
if (code == HttpStatus.tooManyRequests) {
@@ -62,8 +66,9 @@
// Http client doesn't automatically follow 308 (Permanent Redirect).
if (code == HttpStatus.permanentRedirect) {
if (response.headers.containsKey('location')) {
- Uri redirectUri = Uri.parse(link.uri.origin + response.headers['location']);
- return _verifyLink(Link<P>(redirectUri, link.payload));
+ Uri redirectUri =
+ Uri.parse(link.uri.origin + response.headers['location']);
+ return verifyLink(Link<P>(redirectUri, link.payload));
}
return false;
}
diff --git a/tools/doc_checker/test/graph_test.dart b/tools/doc_checker/test/graph_test.dart
new file mode 100644
index 0000000..70734ac
--- /dev/null
+++ b/tools/doc_checker/test/graph_test.dart
@@ -0,0 +1,43 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:doc_checker/graph.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('doc_checker graph tests', () {
+ test('getNode adds new node to graph', () {
+ final Graph graph = Graph();
+ expect(graph.nodeCount, equals(0));
+ graph.getNode('label');
+ expect(graph.nodeCount, equals(1));
+ });
+
+ test('getNode returns existing node', () {
+ final Graph graph = Graph();
+ final Node node1 = graph.getNode('label');
+ final Node node2 = graph.getNode('label');
+ expect(graph.nodeCount, equals(1));
+ expect(node1, equals(node2));
+ });
+
+ test('no orphans with node connected to root', () {
+ final Graph graph = Graph();
+ final Node root = graph.getNode('root');
+ graph.root = root;
+ final Node node = graph.getNode('label');
+ graph.addEdge(from: root, to: node);
+ expect(graph.orphans, hasLength(0));
+ });
+
+ test('unknown node cannot be root', () {
+ final Graph graph = Graph();
+ final Node unknown = graph.getNode('unknown');
+ expect(graph.nodeCount, equals(1));
+ graph.removeSingletons();
+ expect(graph.nodeCount, equals(0));
+ expect(() => graph.root = unknown, throwsException);
+ });
+ });
+}
diff --git a/tools/doc_checker/test/link_scraper_test.dart b/tools/doc_checker/test/link_scraper_test.dart
new file mode 100644
index 0000000..bb431d2
--- /dev/null
+++ b/tools/doc_checker/test/link_scraper_test.dart
@@ -0,0 +1,26 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:doc_checker/link_scraper.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('doc_checker link_scraper tests', () {
+ test('no links found in plain text', () {
+ final List<String> lines = ['this text has no links', 'same here'];
+ expect(LinkScraper().scrapeLines(lines).isEmpty, isTrue);
+ });
+
+ test('links get scraped', () {
+ final List<String> lines = [
+ 'this text has no links',
+ 'this one [does](link.md).',
+ 'but not *this one*'
+ ];
+ Iterable<String> links = LinkScraper().scrapeLines(lines);
+ expect(links, hasLength(1));
+ expect(links.first, equals('link.md'));
+ });
+ });
+}
diff --git a/tools/doc_checker/test/link_verifier_test.dart b/tools/doc_checker/test/link_verifier_test.dart
new file mode 100644
index 0000000..2fd1b74
--- /dev/null
+++ b/tools/doc_checker/test/link_verifier_test.dart
@@ -0,0 +1,63 @@
+// Copyright 2019 The Fuchsia Authors. 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:io';
+
+import 'package:doc_checker/link_verifier.dart';
+import 'package:http/http.dart';
+import 'package:http/testing.dart';
+import 'package:test/test.dart';
+
+void main() {
+ group('doc_checker link_verifier tests', () {
+ test('link to string conversion returns the Uri', () {
+ Link<String> link = Link(Uri.parse('https://www.example.com'), 'label');
+ expect(link.toString(), equals('https://www.example.com'));
+ });
+
+ test('response code 200 is considered valid', () async {
+ Client client = MockClient((request) async {
+ return Response('', HttpStatus.ok);
+ });
+ Link<String> link = Link(Uri.parse('https://www.example.com'), 'label');
+ List<Link<String>> links = <Link<String>>[];
+
+ LinkVerifier<String> linkVerifier = LinkVerifier(links, client);
+ expect(linkVerifier.verifyLink(link), completion(isTrue));
+ });
+
+ test('response code 404 is considered invalid', () async {
+ Client client = MockClient((request) async {
+ return Response('', HttpStatus.notFound);
+ });
+ Link<String> link = Link(Uri.parse('https://www.example.com'), 'label');
+ List<Link<String>> links = <Link<String>>[];
+
+ LinkVerifier<String> linkVerifier = LinkVerifier(links, client);
+ expect(linkVerifier.verifyLink(link), completion(isFalse));
+ });
+
+ test('link verifier follows redirect', () async {
+ final Uri srcUri = Uri.parse('https://www.redirect.com');
+ final Uri destUri = Uri.parse('https://www.redirect.com/newpage');
+ Client client = MockClient((request) async {
+ if (request.url == srcUri) {
+ return Response('', HttpStatus.permanentRedirect,
+ headers: {'location': '/newpage'});
+ } else if (request.url == destUri) {
+ return Response('', HttpStatus.ok);
+ } else {
+ return Response('', HttpStatus.notFound);
+ }
+ });
+
+ Link<String> link = Link(srcUri, 'label');
+ List<Link<String>> links = <Link<String>>[];
+
+ LinkVerifier<String> linkVerifier = LinkVerifier(links, client);
+ var valid = await linkVerifier.verifyLink(link);
+ expect(valid, isTrue);
+ });
+ });
+}