// 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/pubspec_check_command.dart';
import 'package:test/test.dart';

import 'mocks.dart';
import 'util.dart';

/// Returns the top section of a pubspec.yaml for a package named [name].
///
/// By default it will create a header that includes all of the expected
/// values, elements can be changed via arguments to create incorrect
/// entries.
///
/// If [includeRepository] is true, by default the path in the link will
/// be "packages/[name]"; a different "packages"-relative path can be
/// provided with [repositoryPackagesDirRelativePath].
String _headerSection(
  String name, {
  String repository = 'flutter/packages',
  bool includeRepository = true,
  String repositoryBranch = 'main',
  String? repositoryPackagesDirRelativePath,
  bool includeHomepage = false,
  bool includeIssueTracker = true,
  bool publishable = true,
  String? description,
}) {
  final String repositoryPath = repositoryPackagesDirRelativePath ?? name;
  final List<String> repoLinkPathComponents = <String>[
    repository,
    'tree',
    repositoryBranch,
    'packages',
    repositoryPath,
  ];
  final String repoLink =
      'https://github.com/${repoLinkPathComponents.join('/')}';
  final String issueTrackerLink = 'https://github.com/flutter/flutter/issues?'
      'q=is%3Aissue+is%3Aopen+label%3A%22p%3A+$name%22';
  description ??= 'A test package for validating that the pubspec.yaml '
      'follows repo best practices.';
  return '''
name: $name
description: $description
${includeRepository ? 'repository: $repoLink' : ''}
${includeHomepage ? 'homepage: $repoLink' : ''}
${includeIssueTracker ? 'issue_tracker: $issueTrackerLink' : ''}
version: 1.0.0
${publishable ? '' : "publish_to: 'none'"}
''';
}

String _environmentSection({
  String dartConstraint = '>=2.17.0 <4.0.0',
  String? flutterConstraint = '>=3.0.0',
}) {
  return <String>[
    'environment:',
    '  sdk: "$dartConstraint"',
    if (flutterConstraint != null) '  flutter: "$flutterConstraint"',
    '',
  ].join('\n');
}

String _flutterSection({
  bool isPlugin = false,
  String? implementedPackage,
  Map<String, Map<String, String>> pluginPlatformDetails =
      const <String, Map<String, String>>{},
}) {
  String pluginEntry = '''
  plugin:
${implementedPackage == null ? '' : '    implements: $implementedPackage'}
    platforms:
''';

  for (final MapEntry<String, Map<String, String>> platform
      in pluginPlatformDetails.entries) {
    pluginEntry += '''
      ${platform.key}:
''';
    for (final MapEntry<String, String> detail in platform.value.entries) {
      pluginEntry += '''
        ${detail.key}: ${detail.value}
''';
    }
  }

  return '''
flutter:
${isPlugin ? pluginEntry : ''}
''';
}

String _dependenciesSection(
    [List<String> extraDependencies = const <String>[]]) {
  return '''
dependencies:
  flutter:
    sdk: flutter
${extraDependencies.map((String dep) => '  $dep').join('\n')}
''';
}

String _devDependenciesSection(
    [List<String> extraDependencies = const <String>[]]) {
  return '''
dev_dependencies:
  flutter_test:
    sdk: flutter
${extraDependencies.map((String dep) => '  $dep').join('\n')}
''';
}

String _falseSecretsSection() {
  return '''
false_secrets:
  - /lib/main.dart
''';
}

void main() {
  group('test pubspec_check_command', () {
    late CommandRunner<void> runner;
    late RecordingProcessRunner processRunner;
    late FileSystem fileSystem;
    late MockPlatform mockPlatform;
    late Directory packagesDir;

    setUp(() {
      fileSystem = MemoryFileSystem();
      mockPlatform = MockPlatform();
      packagesDir = fileSystem.currentDirectory.childDirectory('packages');
      createPackagesDirectory(parentDir: packagesDir.parent);
      processRunner = RecordingProcessRunner();
      final PubspecCheckCommand command = PubspecCheckCommand(
        packagesDir,
        processRunner: processRunner,
        platform: mockPlatform,
      );

      runner = CommandRunner<void>(
          'pubspec_check_command', 'Test for pubspec_check_command');
      runner.addCommand(command);
    });

    test('passes for a plugin following conventions', () async {
      final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
${_falseSecretsSection()}
''');

      plugin.getExamples().first.pubspecFile.writeAsStringSync('''
${_headerSection(
        'plugin_example',
        publishable: false,
        includeRepository: false,
        includeIssueTracker: false,
      )}
${_environmentSection()}
${_dependenciesSection()}
${_flutterSection()}
''');

      final List<String> output = await runCapturingPrint(runner, <String>[
        'pubspec-check',
      ]);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for plugin...'),
          contains('Running for plugin/example...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('passes for a Flutter package following conventions', () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection()}
${_devDependenciesSection()}
${_flutterSection()}
${_falseSecretsSection()}
''');

      package.getExamples().first.pubspecFile.writeAsStringSync('''
${_headerSection(
        'a_package',
        publishable: false,
        includeRepository: false,
        includeIssueTracker: false,
      )}
${_environmentSection()}
${_dependenciesSection()}
${_flutterSection()}
''');

      final List<String> output = await runCapturingPrint(runner, <String>[
        'pubspec-check',
      ]);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for a_package...'),
          contains('Running for a_package/example...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('passes for a minimal package following conventions', () async {
      final RepositoryPackage package =
          createFakePackage('package', packagesDir, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('package')}
${_environmentSection()}
${_dependenciesSection()}
''');

      final List<String> output = await runCapturingPrint(runner, <String>[
        'pubspec-check',
      ]);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('fails when homepage is included', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', includeHomepage: true)}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Found a "homepage" entry; only "repository" should be used.'),
        ]),
      );
    });

    test('fails when repository is missing', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', includeRepository: false)}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Missing "repository"'),
        ]),
      );
    });

    test('fails when homepage is given instead of repository', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', includeHomepage: true, includeRepository: false)}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Found a "homepage" entry; only "repository" should be used.'),
        ]),
      );
    });

    test('fails when repository package name is incorrect', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', repositoryPackagesDirRelativePath: 'different_plugin')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('The "repository" link should end with the package path.'),
        ]),
      );
    });

    test('fails when repository uses master instead of main', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', repositoryBranch: 'master')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('The "repository" link should start with the repository\'s '
              'main tree: "https://github.com/flutter/packages/tree/main"'),
        ]),
      );
    });

    test('fails when repository is not flutter/packages', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', repository: 'flutter/plugins')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('The "repository" link should start with the repository\'s '
              'main tree: "https://github.com/flutter/packages/tree/main"'),
        ]),
      );
    });

    test('fails when issue tracker is missing', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', includeIssueTracker: false)}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('A package should have an "issue_tracker" link'),
        ]),
      );
    });

    test('fails when description is too short', () async {
      final RepositoryPackage plugin = createFakePlugin(
          'a_plugin', packagesDir.childDirectory('a_plugin'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', description: 'Too short')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('"description" is too short. pub.dev recommends package '
              'descriptions of 60-180 characters.'),
        ]),
      );
    });

    test(
        'allows short descriptions for non-app-facing parts of federated plugins',
        () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', description: 'Too short')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('"description" is too short. pub.dev recommends package '
              'descriptions of 60-180 characters.'),
        ]),
      );
    });

    test('fails when description is too long', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      const String description = 'This description is too long. It just goes '
          'on and on and on and on and on. pub.dev will down-score it because '
          'there is just too much here. Someone shoul really cut this down to just '
          'the core description so that search results are more useful and the '
          'package does not lose pub points.';
      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin', description: description)}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('"description" is too long. pub.dev recommends package '
              'descriptions of 60-180 characters.'),
        ]),
      );
    });

    test('fails when environment section is out of order', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin')}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
${_environmentSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Major sections should follow standard repository ordering:'),
        ]),
      );
    });

    test('fails when flutter section is out of order', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin')}
${_flutterSection(isPlugin: true)}
${_environmentSection()}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Major sections should follow standard repository ordering:'),
        ]),
      );
    });

    test('fails when dependencies section is out of order', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_devDependenciesSection()}
${_dependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Major sections should follow standard repository ordering:'),
        ]),
      );
    });

    test('fails when dev_dependencies section is out of order', () async {
      final RepositoryPackage plugin = createFakePlugin('plugin', packagesDir);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin')}
${_environmentSection()}
${_devDependenciesSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Major sections should follow standard repository ordering:'),
        ]),
      );
    });

    test('fails when false_secrets section is out of order', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_falseSecretsSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Major sections should follow standard repository ordering:'),
        ]),
      );
    });

    test('fails when an implemenation package is missing "implements"',
        () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a_foo', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin_a_foo')}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Missing "implements: plugin_a" in "plugin" section.'),
        ]),
      );
    });

    test('fails when an implemenation package has the wrong "implements"',
        () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a_foo', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin_a_foo')}
${_environmentSection()}
${_flutterSection(isPlugin: true, implementedPackage: 'plugin_a_foo')}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Expecetd "implements: plugin_a"; '
              'found "implements: plugin_a_foo".'),
        ]),
      );
    });

    test('passes for a correct implemenation package', () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a_foo', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection(
        'plugin_a_foo',
        repositoryPackagesDirRelativePath: 'plugin_a/plugin_a_foo',
      )}
${_environmentSection()}
${_flutterSection(isPlugin: true, implementedPackage: 'plugin_a')}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      final List<String> output =
          await runCapturingPrint(runner, <String>['pubspec-check']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for plugin_a_foo...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('fails when a "default_package" looks incorrect', () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection(
        'plugin_a',
        repositoryPackagesDirRelativePath: 'plugin_a/plugin_a',
      )}
${_environmentSection()}
${_flutterSection(
        isPlugin: true,
        pluginPlatformDetails: <String, Map<String, String>>{
          'android': <String, String>{'default_package': 'plugin_b_android'}
        },
      )}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              '"plugin_b_android" is not an expected implementation name for "plugin_a"'),
        ]),
      );
    });

    test(
        'fails when a "default_package" does not have a corresponding dependency',
        () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection(
        'plugin_a',
        repositoryPackagesDirRelativePath: 'plugin_a/plugin_a',
      )}
${_environmentSection()}
${_flutterSection(
        isPlugin: true,
        pluginPlatformDetails: <String, Map<String, String>>{
          'android': <String, String>{'default_package': 'plugin_a_android'}
        },
      )}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('The following default_packages are missing corresponding '
              'dependencies:\n  plugin_a_android'),
        ]),
      );
    });

    test('passes for an app-facing package without "implements"', () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection(
        'plugin_a',
        repositoryPackagesDirRelativePath: 'plugin_a/plugin_a',
      )}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      final List<String> output =
          await runCapturingPrint(runner, <String>['pubspec-check']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for plugin_a/plugin_a...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('passes for a platform interface package without "implements"',
        () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a_platform_interface', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      plugin.pubspecFile.writeAsStringSync('''
${_headerSection(
        'plugin_a_platform_interface',
        repositoryPackagesDirRelativePath:
            'plugin_a/plugin_a_platform_interface',
      )}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      final List<String> output =
          await runCapturingPrint(runner, <String>['pubspec-check']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for plugin_a_platform_interface...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('validates some properties even for unpublished packages', () async {
      final RepositoryPackage plugin = createFakePlugin(
          'plugin_a_foo', packagesDir.childDirectory('plugin_a'),
          examples: <String>[]);

      // Environment section is in the wrong location.
      // Missing 'implements'.
      plugin.pubspecFile.writeAsStringSync('''
${_headerSection('plugin_a_foo', publishable: false)}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
${_environmentSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(
          runner, <String>['pubspec-check'], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains(
              'Major sections should follow standard repository ordering:'),
          contains('Missing "implements: plugin_a" in "plugin" section.'),
        ]),
      );
    });

    test('ignores some checks for unpublished packages', () async {
      final RepositoryPackage plugin =
          createFakePlugin('plugin', packagesDir, examples: <String>[]);

      // Missing metadata that is only useful for published packages, such as
      // repository and issue tracker.
      plugin.pubspecFile.writeAsStringSync('''
${_headerSection(
        'plugin',
        publishable: false,
        includeRepository: false,
        includeIssueTracker: false,
      )}
${_environmentSection()}
${_flutterSection(isPlugin: true)}
${_dependenciesSection()}
${_devDependenciesSection()}
''');

      final List<String> output =
          await runCapturingPrint(runner, <String>['pubspec-check']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for plugin...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('fails when a Flutter package has a too-low minimum Flutter version',
        () async {
      final RepositoryPackage package = createFakePackage(
          'a_package', packagesDir,
          isFlutter: true, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection(flutterConstraint: '>=2.10.0')}
${_dependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(runner, <String>[
        'pubspec-check',
        '--min-min-flutter-version',
        '3.0.0'
      ], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Minimum allowed Flutter version 2.10.0 is less than 3.0.0'),
        ]),
      );
    });

    test(
        'passes when a Flutter package requires exactly the minimum Flutter version',
        () async {
      final RepositoryPackage package = createFakePackage(
          'a_package', packagesDir,
          isFlutter: true, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection(flutterConstraint: '>=3.3.0', dartConstraint: '>=2.18.0 <4.0.0')}
${_dependenciesSection()}
''');

      final List<String> output = await runCapturingPrint(runner,
          <String>['pubspec-check', '--min-min-flutter-version', '3.3.0']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for a_package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test(
        'passes when a Flutter package requires a higher minimum Flutter version',
        () async {
      final RepositoryPackage package = createFakePackage(
          'a_package', packagesDir,
          isFlutter: true, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection(flutterConstraint: '>=3.7.0', dartConstraint: '>=2.19.0 <4.0.0')}
${_dependenciesSection()}
''');

      final List<String> output = await runCapturingPrint(runner,
          <String>['pubspec-check', '--min-min-flutter-version', '3.3.0']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for a_package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('fails when a non-Flutter package has a too-low minimum Dart version',
        () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection(dartConstraint: '>=2.14.0 <4.0.0', flutterConstraint: null)}
${_dependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(runner, <String>[
        'pubspec-check',
        '--min-min-flutter-version',
        '3.0.0'
      ], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Minimum allowed Dart version 2.14.0 is less than 2.17.0'),
        ]),
      );
    });

    test(
        'passes when a non-Flutter package requires exactly the minimum Dart version',
        () async {
      final RepositoryPackage package = createFakePackage(
          'a_package', packagesDir,
          isFlutter: true, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection(dartConstraint: '>=2.18.0 <4.0.0', flutterConstraint: null)}
${_dependenciesSection()}
''');

      final List<String> output = await runCapturingPrint(runner,
          <String>['pubspec-check', '--min-min-flutter-version', '3.3.0']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for a_package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test(
        'passes when a non-Flutter package requires a higher minimum Dart version',
        () async {
      final RepositoryPackage package = createFakePackage(
          'a_package', packagesDir,
          isFlutter: true, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection(dartConstraint: '>=2.18.0 <4.0.0', flutterConstraint: null)}
${_dependenciesSection()}
''');

      final List<String> output = await runCapturingPrint(runner,
          <String>['pubspec-check', '--min-min-flutter-version', '3.0.0']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for a_package...'),
          contains('No issues found!'),
        ]),
      );
    });

    test('fails when a Flutter->Dart SDK version mapping is missing', () async {
      final RepositoryPackage package =
          createFakePackage('a_package', packagesDir, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(runner, <String>[
        'pubspec-check',
        '--min-min-flutter-version',
        '2.0.0'
      ], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Dart SDK version for Fluter SDK version 2.0.0 is unknown'),
        ]),
      );
    });

    test(
        'fails when a Flutter package has a too-low minimum Dart version for '
        'the corresponding minimum Flutter version', () async {
      final RepositoryPackage package = createFakePackage(
          'a_package', packagesDir,
          isFlutter: true, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection(flutterConstraint: '>=3.3.0', dartConstraint: '>=2.16.0 <4.0.0')}
${_dependenciesSection()}
''');

      Error? commandError;
      final List<String> output = await runCapturingPrint(runner, <String>[
        'pubspec-check',
      ], errorHandler: (Error e) {
        commandError = e;
      });

      expect(commandError, isA<ToolExit>());
      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('The minimum Dart version is 2.16.0, but the '
              'minimum Flutter version of 3.3.0 shipped with '
              'Dart 2.18.0. Please use consistent lower SDK '
              'bounds'),
        ]),
      );
    });

    group('dependency check', () {
      test('passes for local dependencies', () async {
        final RepositoryPackage package =
            createFakePackage('a_package', packagesDir);
        final RepositoryPackage dependencyPackage =
            createFakePackage('local_dependency', packagesDir);

        package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection(<String>['local_dependency: ^1.0.0'])}
''');
        dependencyPackage.pubspecFile.writeAsStringSync('''
${_headerSection('local_dependency')}
${_environmentSection()}
${_dependenciesSection()}
''');

        final List<String> output =
            await runCapturingPrint(runner, <String>['pubspec-check']);

        expect(
          output,
          containsAllInOrder(<Matcher>[
            contains('Running for a_package...'),
            contains('No issues found!'),
          ]),
        );
      });

      test('fails when an unexpected dependency is found', () async {
        final RepositoryPackage package =
            createFakePackage('a_package', packagesDir, examples: <String>[]);

        package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection(<String>['bad_dependency: ^1.0.0'])}
''');

        Error? commandError;
        final List<String> output = await runCapturingPrint(runner, <String>[
          'pubspec-check',
        ], errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
          output,
          containsAllInOrder(<Matcher>[
            contains(
                'The following unexpected non-local dependencies were found:\n'
                '  bad_dependency\n'
                'Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies '
                'for more information and next steps.'),
          ]),
        );
      });

      test('fails when an unexpected dev dependency is found', () async {
        final RepositoryPackage package =
            createFakePackage('a_package', packagesDir, examples: <String>[]);

        package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection()}
${_devDependenciesSection(<String>['bad_dependency: ^1.0.0'])}
''');

        Error? commandError;
        final List<String> output = await runCapturingPrint(runner, <String>[
          'pubspec-check',
        ], errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
          output,
          containsAllInOrder(<Matcher>[
            contains(
                'The following unexpected non-local dependencies were found:\n'
                '  bad_dependency\n'
                'Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies '
                'for more information and next steps.'),
          ]),
        );
      });

      test('passes when a dependency is on the allow list', () async {
        final RepositoryPackage package =
            createFakePackage('a_package', packagesDir);

        package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection(<String>['allowed: ^1.0.0'])}
''');

        final List<String> output = await runCapturingPrint(runner,
            <String>['pubspec-check', '--allow-dependencies', 'allowed']);

        expect(
          output,
          containsAllInOrder(<Matcher>[
            contains('Running for a_package...'),
            contains('No issues found!'),
          ]),
        );
      });

      test('passes when a pinned dependency is on the pinned allow list',
          () async {
        final RepositoryPackage package =
            createFakePackage('a_package', packagesDir);

        package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection(<String>['allow_pinned: 1.0.0'])}
''');

        final List<String> output = await runCapturingPrint(runner, <String>[
          'pubspec-check',
          '--allow-pinned-dependencies',
          'allow_pinned'
        ]);

        expect(
          output,
          containsAllInOrder(<Matcher>[
            contains('Running for a_package...'),
            contains('No issues found!'),
          ]),
        );
      });

      test('fails when an allowed-when-pinned dependency is unpinned',
          () async {
        final RepositoryPackage package =
            createFakePackage('a_package', packagesDir);

        package.pubspecFile.writeAsStringSync('''
${_headerSection('a_package')}
${_environmentSection()}
${_dependenciesSection(<String>['allow_pinned: ^1.0.0'])}
''');

        Error? commandError;
        final List<String> output = await runCapturingPrint(runner, <String>[
          'pubspec-check',
          '--allow-pinned-dependencies',
          'allow_pinned'
        ], errorHandler: (Error e) {
          commandError = e;
        });

        expect(commandError, isA<ToolExit>());
        expect(
          output,
          containsAllInOrder(<Matcher>[
            contains(
                'The following unexpected non-local dependencies were found:\n'
                '  allow_pinned\n'
                'Please see https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies '
                'for more information and next steps.'),
          ]),
        );
      });
    });
  });

  group('test pubspec_check_command on Windows', () {
    late CommandRunner<void> runner;
    late RecordingProcessRunner processRunner;
    late FileSystem fileSystem;
    late MockPlatform mockPlatform;
    late Directory packagesDir;

    setUp(() {
      fileSystem = MemoryFileSystem(style: FileSystemStyle.windows);
      mockPlatform = MockPlatform(isWindows: true);
      packagesDir = fileSystem.currentDirectory.childDirectory('packages');
      createPackagesDirectory(parentDir: packagesDir.parent);
      processRunner = RecordingProcessRunner();
      final PubspecCheckCommand command = PubspecCheckCommand(
        packagesDir,
        processRunner: processRunner,
        platform: mockPlatform,
      );

      runner = CommandRunner<void>(
          'pubspec_check_command', 'Test for pubspec_check_command');
      runner.addCommand(command);
    });

    test('repository check works', () async {
      final RepositoryPackage package =
          createFakePackage('package', packagesDir, examples: <String>[]);

      package.pubspecFile.writeAsStringSync('''
${_headerSection('package')}
${_environmentSection()}
${_dependenciesSection()}
''');

      final List<String> output =
          await runCapturingPrint(runner, <String>['pubspec-check']);

      expect(
        output,
        containsAllInOrder(<Matcher>[
          contains('Running for package...'),
          contains('No issues found!'),
        ]),
      );
    });
  });
}