// Copyright 2014 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.

@TestOn('mac-os')
library;

import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
import '../suite_runners/run_verify_binaries_codesigned_tests.dart';
import './common.dart';

void main() async {
  const flutterRoot = '/a/b/c';
  final List<String> allExpectedFiles =
      binariesWithEntitlements(flutterRoot) + binariesWithoutEntitlements(flutterRoot);
  final String allFilesStdout = allExpectedFiles.join('\n');
  final List<String> allExpectedXcframeworks = signedXcframeworks(flutterRoot);
  final String allXcframeworksStdout = allExpectedXcframeworks.join('\n');
  final List<String> withEntitlements = binariesWithEntitlements(flutterRoot);

  group('verifyExist', () {
    test('Not all files found', () async {
      final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
        const FakeCommand(
          command: <String>['find', '/a/b/c/bin/cache', '-type', 'f'],
          stdout: '/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
        ),
        const FakeCommand(
          command: <String>[
            'file',
            '--mime-type',
            '-b',
            '/a/b/c/bin/cache/artifacts/engine/android-arm-profile/darwin-x64/gen_snapshot',
          ],
          stdout: 'application/x-mach-binary',
        ),
      ]);
      expect(
        () async => verifyExist(flutterRoot, processManager: processManager),
        throwsExceptionWith('Did not find all expected binaries!'),
      );
    });

    test('All files found', () async {
      final commandList = <FakeCommand>[];
      final findCmd = FakeCommand(
        command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
        stdout: allFilesStdout,
      );
      commandList.add(findCmd);
      for (final expectedFile in allExpectedFiles) {
        commandList.add(
          FakeCommand(
            command: <String>['file', '--mime-type', '-b', expectedFile],
            stdout: 'application/x-mach-binary',
          ),
        );
      }
      final ProcessManager processManager = FakeProcessManager.list(commandList);
      await expectLater(verifyExist('/a/b/c', processManager: processManager), completes);
    });
  });

  group('find paths', () {
    test('All binary files found', () async {
      final commandList = <FakeCommand>[];
      final findCmd = FakeCommand(
        command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
        stdout: allFilesStdout,
      );
      commandList.add(findCmd);
      for (final expectedFile in allExpectedFiles) {
        commandList.add(
          FakeCommand(
            command: <String>['file', '--mime-type', '-b', expectedFile],
            stdout: 'application/x-mach-binary',
          ),
        );
      }
      final ProcessManager processManager = FakeProcessManager.list(commandList);
      final List<String> foundFiles = await findBinaryPaths(
        '$flutterRoot/bin/cache',
        processManager: processManager,
      );
      expect(foundFiles, allExpectedFiles);
    });

    test('Empty file list', () async {
      final commandList = <FakeCommand>[];
      const findCmd = FakeCommand(
        command: <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
      );
      commandList.add(findCmd);
      final ProcessManager processManager = FakeProcessManager.list(commandList);
      final List<String> foundFiles = await findBinaryPaths(
        '$flutterRoot/bin/cache',
        processManager: processManager,
      );
      expect(foundFiles, <String>[]);
    });

    test('All xcframeworks files found', () async {
      final commandList = <FakeCommand>[
        FakeCommand(
          command: const <String>[
            'find',
            '$flutterRoot/bin/cache',
            '-type',
            'd',
            '-name',
            '*xcframework',
          ],
          stdout: allXcframeworksStdout,
        ),
      ];
      final ProcessManager processManager = FakeProcessManager.list(commandList);
      final List<String> foundFiles = await findXcframeworksPaths(
        '$flutterRoot/bin/cache',
        processManager: processManager,
      );
      expect(foundFiles, allExpectedXcframeworks);
    });

    group('isBinary', () {
      test('isTrue', () async {
        final commandList = <FakeCommand>[];
        const fileToCheck = '/a/b/c/one.zip';
        const findCmd = FakeCommand(
          command: <String>['file', '--mime-type', '-b', fileToCheck],
          stdout: 'application/x-mach-binary',
        );
        commandList.add(findCmd);
        final ProcessManager processManager = FakeProcessManager.list(commandList);
        final bool result = await isBinary(fileToCheck, processManager: processManager);
        expect(result, isTrue);
      });

      test('isFalse', () async {
        final commandList = <FakeCommand>[];
        const fileToCheck = '/a/b/c/one.zip';
        const findCmd = FakeCommand(
          command: <String>['file', '--mime-type', '-b', fileToCheck],
          stdout: 'text/xml',
        );
        commandList.add(findCmd);
        final ProcessManager processManager = FakeProcessManager.list(commandList);
        final bool result = await isBinary(fileToCheck, processManager: processManager);
        expect(result, isFalse);
      });
    });

    group('hasExpectedEntitlements', () {
      test('expected entitlements', () async {
        final commandList = <FakeCommand>[];
        const fileToCheck = '/a/b/c/one.zip';
        const codesignCmd = FakeCommand(
          command: <String>['codesign', '--display', '--entitlements', ':-', fileToCheck],
        );
        commandList.add(codesignCmd);
        final ProcessManager processManager = FakeProcessManager.list(commandList);
        final bool result = await hasExpectedEntitlements(
          fileToCheck,
          flutterRoot,
          processManager: processManager,
        );
        expect(result, isTrue);
      });

      test('unexpected entitlements', () async {
        final commandList = <FakeCommand>[];
        const fileToCheck = '/a/b/c/one.zip';
        const codesignCmd = FakeCommand(
          command: <String>['codesign', '--display', '--entitlements', ':-', fileToCheck],
          exitCode: 1,
        );
        commandList.add(codesignCmd);
        final ProcessManager processManager = FakeProcessManager.list(commandList);
        final bool result = await hasExpectedEntitlements(
          fileToCheck,
          flutterRoot,
          processManager: processManager,
        );
        expect(result, isFalse);
      });
    });
  });

  group('verifySignatures', () {
    test('succeeds if every binary is codesigned and has correct entitlements', () async {
      final commandList = <FakeCommand>[];
      final findCmd = FakeCommand(
        command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
        stdout: allFilesStdout,
      );
      commandList.add(findCmd);
      for (final expectedFile in allExpectedFiles) {
        commandList.add(
          FakeCommand(
            command: <String>['file', '--mime-type', '-b', expectedFile],
            stdout: 'application/x-mach-binary',
          ),
        );
      }
      commandList.add(
        FakeCommand(
          command: const <String>[
            'find',
            '$flutterRoot/bin/cache',
            '-type',
            'd',
            '-name',
            '*xcframework',
          ],
          stdout: allXcframeworksStdout,
        ),
      );
      for (final expectedFile in allExpectedFiles) {
        commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedFile]));
        if (withEntitlements.contains(expectedFile)) {
          commandList.add(
            FakeCommand(
              command: <String>['codesign', '--display', '--entitlements', ':-', expectedFile],
              stdout: expectedEntitlements.join('\n'),
            ),
          );
        }
      }

      for (final expectedXcframework in allExpectedXcframeworks) {
        commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedXcframework]));
      }
      final ProcessManager processManager = FakeProcessManager.list(commandList);
      await expectLater(verifySignatures(flutterRoot, processManager: processManager), completes);
    });

    test('fails if binaries do not have the right entitlements', () async {
      final commandList = <FakeCommand>[];
      final findCmd = FakeCommand(
        command: const <String>['find', '$flutterRoot/bin/cache', '-type', 'f'],
        stdout: allFilesStdout,
      );
      commandList.add(findCmd);
      for (final expectedFile in allExpectedFiles) {
        commandList.add(
          FakeCommand(
            command: <String>['file', '--mime-type', '-b', expectedFile],
            stdout: 'application/x-mach-binary',
          ),
        );
      }
      commandList.add(
        FakeCommand(
          command: const <String>[
            'find',
            '$flutterRoot/bin/cache',
            '-type',
            'd',
            '-name',
            '*xcframework',
          ],
          stdout: allXcframeworksStdout,
        ),
      );
      for (final expectedFile in allExpectedFiles) {
        commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedFile]));
        if (withEntitlements.contains(expectedFile)) {
          commandList.add(
            FakeCommand(
              command: <String>['codesign', '--display', '--entitlements', ':-', expectedFile],
            ),
          );
        }
      }
      for (final expectedXcframework in allExpectedXcframeworks) {
        commandList.add(FakeCommand(command: <String>['codesign', '-vvv', expectedXcframework]));
      }
      final ProcessManager processManager = FakeProcessManager.list(commandList);

      expect(
        () async => verifySignatures(flutterRoot, processManager: processManager),
        throwsExceptionWith('Test failed because files found with the wrong entitlements'),
      );
    });
  });

  group('verifyFatBinaries', () {
    test('succeeds if every binary is fat', () async {
      final commandList = <FakeCommand>[];
      final List<String> fatBinaries =
          presignedBinariesWithEntitlements(flutterRoot) +
          presignedBinariesWithoutEntitlements(flutterRoot);

      for (final binaryPath in fatBinaries) {
        commandList.add(
          FakeCommand(
            command: <String>['file', binaryPath],
            stdout: 'Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]',
          ),
        );
      }
      final ProcessManager processManager = FakeProcessManager.list(commandList);
      await expectLater(verifyFatBinaries(flutterRoot, processManager: processManager), completes);
    });

    test('fails if a binary is not fat', () async {
      final commandList = <FakeCommand>[];
      final List<String> fatBinaries =
          presignedBinariesWithEntitlements(flutterRoot) +
          presignedBinariesWithoutEntitlements(flutterRoot);

      if (fatBinaries.isNotEmpty) {
        commandList.add(
          FakeCommand(
            command: <String>['file', fatBinaries.first],
            stdout: 'Mach-O 64-bit executable x86_64',
          ),
        );
        for (final String binaryPath in fatBinaries.skip(1)) {
          commandList.add(
            FakeCommand(
              command: <String>['file', binaryPath],
              stdout: 'Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]',
            ),
          );
        }
      }
      final ProcessManager processManager = FakeProcessManager.list(commandList);
      expect(
        () async => verifyFatBinaries(flutterRoot, processManager: processManager),
        throwsExceptionWith('Failed fat binary verification for:'),
      );
    });
  });
}