// Copyright 2013 The Flutter 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:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_plugin_tools/src/common/core.dart'; import 'package:flutter_plugin_tools/src/repo_package_info_check_command.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import 'common/package_command_test.mocks.dart'; import 'util.dart'; void main() { late CommandRunner runner; late FileSystem fileSystem; late Directory root; late Directory packagesDir; setUp(() { fileSystem = MemoryFileSystem(); root = fileSystem.currentDirectory; packagesDir = root.childDirectory('packages'); final MockGitDir gitDir = MockGitDir(); when(gitDir.path).thenReturn(root.path); final RepoPackageInfoCheckCommand command = RepoPackageInfoCheckCommand( packagesDir, gitDir: gitDir, ); runner = CommandRunner( 'dependabot_test', 'Test for $RepoPackageInfoCheckCommand'); runner.addCommand(command); }); String readmeTableHeader() { return ''' | Package | Pub | Points | Popularity | Issues | Pull requests | |---------|-----|--------|------------|--------|---------------| '''; } void writeCodeOwners(List ownedPackages) { final List subpaths = ownedPackages .map((RepositoryPackage p) => p.isFederated ? [p.directory.parent.basename, p.directory.basename] .join('/') : p.directory.basename) .toList(); root.childFile('CODEOWNERS').writeAsStringSync(''' ${subpaths.map((String subpath) => 'packages/$subpath/** @someone').join('\n')} '''); } String readmeTableEntry(String packageName) { final String encodedTag = Uri.encodeComponent('p: $packageName'); return '| [$packageName](./packages/$packageName/) | ' '[![pub package](https://img.shields.io/pub/v/$packageName.svg)](https://pub.dev/packages/$packageName) | ' '[![pub points](https://img.shields.io/pub/points/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![popularity](https://img.shields.io/pub/popularity/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$encodedTag?label=)](https://github.com/flutter/flutter/labels/$encodedTag) | ' '[![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/$encodedTag?label=)](https://github.com/flutter/packages/labels/$encodedTag) |'; } test('passes for correct README coverage', () async { final List packages = [ createFakePackage('a_package', packagesDir), ]; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} ${readmeTableEntry('a_package')} '''); writeCodeOwners(packages); final List output = await runCapturingPrint(runner, ['repo-package-info-check']); expect(output, containsAllInOrder([contains('Ran for 1 package(s)')])); }); test('passes for federated plugins with only app-facing package listed', () async { const String pluginName = 'foo'; final Directory pluginDir = packagesDir.childDirectory(pluginName); final List packages = [ createFakePlugin(pluginName, pluginDir), createFakePlugin('${pluginName}_platform_interface', pluginDir), createFakePlugin('${pluginName}_android', pluginDir), createFakePlugin('${pluginName}_ios', pluginDir), ]; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} ${readmeTableEntry(pluginName)} '''); writeCodeOwners(packages); final List output = await runCapturingPrint(runner, ['repo-package-info-check']); expect(output, containsAllInOrder([contains('Ran for 4 package(s)')])); }); test('fails for unexpected README table entry', () async { final List packages = [ createFakePackage('a_package', packagesDir), ]; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} ${readmeTableEntry('another_package')} '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Unknown package "another_package" in root README.md table'), ])); }); test('fails for missing README table entry', () async { final List packages = [ createFakePackage('a_package', packagesDir), createFakePackage('another_package', packagesDir), ]; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} ${readmeTableEntry('another_package')} '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Missing repo root README.md table entry'), contains('a_package:\n' ' Missing repo root README.md table entry') ])); }); test('fails for unexpected format in README table entry', () async { const String packageName = 'a_package'; final String encodedTag = Uri.encodeComponent('p: $packageName'); final List packages = [ createFakePackage('a_package', packagesDir), ]; final String entry = '| [$packageName](./packages/$packageName/) | ' 'Some random text | ' '[![pub points](https://img.shields.io/pub/points/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![popularity](https://img.shields.io/pub/popularity/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$encodedTag?label=)](https://github.com/flutter/flutter/labels/$encodedTag) | ' '[![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/$encodedTag?label=)](https://github.com/flutter/packages/labels/$encodedTag) |'; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} $entry '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains( 'Invalid repo root README.md table entry: "Some random text"'), contains('a_package:\n' ' Invalid root README.md table entry') ])); }); test('fails for incorrect source link in README table entry', () async { const String packageName = 'a_package'; final String encodedTag = Uri.encodeComponent('p: $packageName'); const String incorrectPackageName = 'a_pakage'; final List packages = [ createFakePackage('a_package', packagesDir), ]; final String entry = '| [$packageName](./packages/$incorrectPackageName/) | ' '[![pub package](https://img.shields.io/pub/v/$packageName.svg)](https://pub.dev/packages/$packageName) | ' '[![pub points](https://img.shields.io/pub/points/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![popularity](https://img.shields.io/pub/popularity/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$encodedTag?label=)](https://github.com/flutter/flutter/labels/$encodedTag) | ' '[![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/$encodedTag?label=)](https://github.com/flutter/packages/labels/$encodedTag) |'; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} $entry '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains( 'Incorrect link in root README.md table: "./packages/$incorrectPackageName/"'), contains('a_package:\n' ' Incorrect link in root README.md table') ])); }); test('fails for incorrect packages/* link in README table entry', () async { const String packageName = 'a_package'; final String encodedTag = Uri.encodeComponent('p: $packageName'); const String incorrectPackageName = 'a_pakage'; final List packages = [ createFakePackage('a_package', packagesDir), ]; final String entry = '| [$packageName](./packages/$packageName/) | ' '[![pub package](https://img.shields.io/pub/v/$packageName.svg)](https://pub.dev/packages/$packageName) | ' '[![pub points](https://img.shields.io/pub/points/$packageName)](https://pub.dev/packages/$incorrectPackageName/score) | ' '[![popularity](https://img.shields.io/pub/popularity/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$encodedTag?label=)](https://github.com/flutter/flutter/labels/$encodedTag) | ' '[![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/$encodedTag?label=)](https://github.com/flutter/packages/labels/$encodedTag) |'; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} $entry '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains( 'Incorrect link in root README.md table: "https://pub.dev/packages/$incorrectPackageName/score"'), contains('a_package:\n' ' Incorrect link in root README.md table') ])); }); test('fails for incorrect labels/* link in README table entry', () async { const String packageName = 'a_package'; final String encodedTag = Uri.encodeComponent('p: $packageName'); final String incorrectTag = Uri.encodeComponent('p: a_pakage'); final List packages = [ createFakePackage('a_package', packagesDir), ]; final String entry = '| [$packageName](./packages/$packageName/) | ' '[![pub package](https://img.shields.io/pub/v/$packageName.svg)](https://pub.dev/packages/$packageName) | ' '[![pub points](https://img.shields.io/pub/points/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![popularity](https://img.shields.io/pub/popularity/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$encodedTag?label=)](https://github.com/flutter/flutter/labels/$incorrectTag) | ' '[![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/$encodedTag?label=)](https://github.com/flutter/packages/labels/$encodedTag) |'; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} $entry '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains( 'Incorrect link in root README.md table: "https://github.com/flutter/flutter/labels/$incorrectTag"'), contains('a_package:\n' ' Incorrect link in root README.md table') ])); }); test('fails for incorrect packages/* anchor in README table entry', () async { const String packageName = 'a_package'; final String encodedTag = Uri.encodeComponent('p: $packageName'); const String incorrectPackageName = 'a_pakage'; final List packages = [ createFakePackage('a_package', packagesDir), ]; final String entry = '| [$packageName](./packages/$packageName/) | ' '[![pub package](https://img.shields.io/pub/v/$packageName.svg)](https://pub.dev/packages/$packageName) | ' '[![pub points](https://img.shields.io/pub/points/$incorrectPackageName)](https://pub.dev/packages/$packageName/score) | ' '[![popularity](https://img.shields.io/pub/popularity/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$encodedTag?label=)](https://github.com/flutter/flutter/labels/$encodedTag) | ' '[![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/$encodedTag?label=)](https://github.com/flutter/packages/labels/$encodedTag) |'; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} $entry '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains( 'Incorrect anchor in root README.md table: "![pub points](https://img.shields.io/pub/points/$incorrectPackageName)"'), contains('a_package:\n' ' Incorrect anchor in root README.md table') ])); }); test('fails for incorrect tag query anchor in README table entry', () async { const String packageName = 'a_package'; final String encodedTag = Uri.encodeComponent('p: $packageName'); final String incorrectTag = Uri.encodeComponent('p: a_pakage'); final List packages = [ createFakePackage('a_package', packagesDir), ]; final String entry = '| [$packageName](./packages/$packageName/) | ' '[![pub package](https://img.shields.io/pub/v/$packageName.svg)](https://pub.dev/packages/$packageName) | ' '[![pub points](https://img.shields.io/pub/points/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![popularity](https://img.shields.io/pub/popularity/$packageName)](https://pub.dev/packages/$packageName/score) | ' '[![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$incorrectTag?label=)](https://github.com/flutter/flutter/labels/$encodedTag) | ' '[![GitHub pull requests by-label](https://img.shields.io/github/issues-pr/flutter/packages/$encodedTag?label=)](https://github.com/flutter/packages/labels/$encodedTag) |'; root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} $entry '''); writeCodeOwners(packages); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains( 'Incorrect anchor in root README.md table: "![GitHub issues by-label](https://img.shields.io/github/issues/flutter/flutter/$incorrectTag?label=)'), contains('a_package:\n' ' Incorrect anchor in root README.md table') ])); }); test('fails for missing CODEOWNER', () async { const String packageName = 'a_package'; createFakePackage(packageName, packagesDir); root.childFile('README.md').writeAsStringSync(''' ${readmeTableHeader()} ${readmeTableEntry(packageName)} '''); writeCodeOwners([]); Error? commandError; final List output = await runCapturingPrint( runner, ['repo-package-info-check'], errorHandler: (Error e) { commandError = e; }); expect(commandError, isA()); expect( output, containsAllInOrder([ contains('Missing CODEOWNERS entry.'), contains('a_package:\n' ' Missing CODEOWNERS entry') ])); }); }