user = $this->createMock(IUser::class); $this->providerLoader = $this->createMock(ProviderLoader::class); $this->providerRegistry = $this->createMock(IRegistry::class); $this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class); $this->session = $this->createMock(ISession::class); $this->config = $this->createMock(IConfig::class); $this->activityManager = $this->createMock(IManager::class); $this->logger = $this->createMock(LoggerInterface::class); $this->tokenProvider = $this->createMock(TokenProvider::class); $this->timeFactory = $this->createMock(ITimeFactory::class); $this->dispatcher = $this->createMock(IEventDispatcher::class); $this->manager = new Manager( $this->providerLoader, $this->providerRegistry, $this->mandatoryTwoFactor, $this->session, $this->config, $this->activityManager, $this->logger, $this->tokenProvider, $this->timeFactory, $this->dispatcher, ); $this->fakeProvider = $this->createMock(IProvider::class); $this->fakeProvider->method('getId')->willReturn('email'); $this->backupProvider = $this->getMockBuilder('\OCP\Authentication\TwoFactorAuth\IProvider')->getMock(); $this->backupProvider->method('getId')->willReturn('backup_codes'); $this->backupProvider->method('isTwoFactorAuthEnabledForUser')->willReturn(true); } private function prepareNoProviders() { $this->providerLoader->method('getProviders') ->with($this->user) ->willReturn([]); } private function prepareProviders() { $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->with($this->user) ->willReturn([ $this->fakeProvider->getId() => true, ]); $this->providerLoader->expects($this->once()) ->method('getProviders') ->with($this->user) ->willReturn([$this->fakeProvider]); } private function prepareProvidersWitBackupProvider() { $this->providerLoader->method('getProviders') ->with($this->user) ->willReturn([ $this->fakeProvider, $this->backupProvider, ]); } public function testIsTwoFactorAuthenticatedEnforced(): void { $this->mandatoryTwoFactor->expects($this->once()) ->method('isEnforcedFor') ->with($this->user) ->willReturn(true); $enabled = $this->manager->isTwoFactorAuthenticated($this->user); $this->assertTrue($enabled); } public function testIsTwoFactorAuthenticatedNoProviders(): void { $this->mandatoryTwoFactor->expects($this->once()) ->method('isEnforcedFor') ->with($this->user) ->willReturn(false); $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->willReturn([]); // No providers registered $this->providerLoader->expects($this->once()) ->method('getProviders') ->willReturn([]); // No providers loadable $this->assertFalse($this->manager->isTwoFactorAuthenticated($this->user)); } public function testIsTwoFactorAuthenticatedOnlyBackupCodes(): void { $this->mandatoryTwoFactor->expects($this->once()) ->method('isEnforcedFor') ->with($this->user) ->willReturn(false); $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->willReturn([ 'backup_codes' => true, ]); $backupCodesProvider = $this->createMock(IProvider::class); $backupCodesProvider ->method('getId') ->willReturn('backup_codes'); $this->providerLoader->expects($this->once()) ->method('getProviders') ->willReturn([ $backupCodesProvider, ]); $this->assertFalse($this->manager->isTwoFactorAuthenticated($this->user)); } public function testIsTwoFactorAuthenticatedFailingProviders(): void { $this->mandatoryTwoFactor->expects($this->once()) ->method('isEnforcedFor') ->with($this->user) ->willReturn(false); $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->willReturn([ 'twofactor_totp' => true, 'twofactor_u2f' => false, ]); // Two providers registered, but … $this->providerLoader->expects($this->once()) ->method('getProviders') ->willReturn([]); // … none of them is able to load, however … // … 2FA is still enforced $this->assertTrue($this->manager->isTwoFactorAuthenticated($this->user)); } public function providerStatesFixData(): array { return [ [false, false], [true, true], ]; } /** * If the 2FA registry has not been populated when a user logs in, * the 2FA manager has to first fix the state before it checks for * enabled providers. * * If any of these providers is active, 2FA is enabled * * @dataProvider providerStatesFixData */ public function testIsTwoFactorAuthenticatedFixesProviderStates(bool $providerEnabled, bool $expected): void { $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->willReturn([]); // Nothing registered yet $this->providerLoader->expects($this->once()) ->method('getProviders') ->willReturn([ $this->fakeProvider ]); $this->fakeProvider->expects($this->once()) ->method('isTwoFactorAuthEnabledForUser') ->with($this->user) ->willReturn($providerEnabled); if ($providerEnabled) { $this->providerRegistry->expects($this->once()) ->method('enableProviderFor') ->with( $this->fakeProvider, $this->user ); } else { $this->providerRegistry->expects($this->once()) ->method('disableProviderFor') ->with( $this->fakeProvider, $this->user ); } $this->assertEquals($expected, $this->manager->isTwoFactorAuthenticated($this->user)); } public function testGetProvider(): void { $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->with($this->user) ->willReturn([ $this->fakeProvider->getId() => true, ]); $this->providerLoader->expects($this->once()) ->method('getProviders') ->with($this->user) ->willReturn([$this->fakeProvider]); $provider = $this->manager->getProvider($this->user, $this->fakeProvider->getId()); $this->assertSame($this->fakeProvider, $provider); } public function testGetInvalidProvider(): void { $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->with($this->user) ->willReturn([]); $this->providerLoader->expects($this->once()) ->method('getProviders') ->with($this->user) ->willReturn([]); $provider = $this->manager->getProvider($this->user, 'nonexistent'); $this->assertNull($provider); } public function testGetLoginSetupProviders(): void { $provider1 = $this->createMock(IProvider::class); $provider2 = $this->createMock(IActivatableAtLogin::class); $this->providerLoader->expects($this->once()) ->method('getProviders') ->with($this->user) ->willReturn([ $provider1, $provider2, ]); $providers = $this->manager->getLoginSetupProviders($this->user); $this->assertCount(1, $providers); $this->assertSame($provider2, reset($providers)); } public function testGetProviders(): void { $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->with($this->user) ->willReturn([ $this->fakeProvider->getId() => true, ]); $this->providerLoader->expects($this->once()) ->method('getProviders') ->with($this->user) ->willReturn([$this->fakeProvider]); $expectedProviders = [ 'email' => $this->fakeProvider, ]; $providerSet = $this->manager->getProviderSet($this->user); $providers = $providerSet->getProviders(); $this->assertEquals($expectedProviders, $providers); $this->assertFalse($providerSet->isProviderMissing()); } public function testGetProvidersOneMissing(): void { $this->providerRegistry->expects($this->once()) ->method('getProviderStates') ->with($this->user) ->willReturn([ $this->fakeProvider->getId() => true, ]); $this->providerLoader->expects($this->once()) ->method('getProviders') ->with($this->user) ->willReturn([]); $expectedProviders = [ 'email' => $this->fakeProvider, ]; $providerSet = $this->manager->getProviderSet($this->user); $this->assertTrue($providerSet->isProviderMissing()); } public function testVerifyChallenge(): void { $this->prepareProviders(); $challenge = 'passme'; $event = $this->createMock(IEvent::class); $this->fakeProvider->expects($this->once()) ->method('verifyChallenge') ->with($this->user, $challenge) ->willReturn(true); $this->session->expects($this->once()) ->method('get') ->with('two_factor_remember_login') ->willReturn(false); $this->session->expects($this->exactly(2)) ->method('remove') ->withConsecutive( ['two_factor_auth_uid'], ['two_factor_remember_login'] ); $this->session->expects($this->once()) ->method('set') ->with(Manager::SESSION_UID_DONE, 'jos'); $this->session->method('getId') ->willReturn('mysessionid'); $this->activityManager->expects($this->once()) ->method('generateEvent') ->willReturn($event); $this->user->expects($this->any()) ->method('getUID') ->willReturn('jos'); $event->expects($this->once()) ->method('setApp') ->with($this->equalTo('core')) ->willReturnSelf(); $event->expects($this->once()) ->method('setType') ->with($this->equalTo('security')) ->willReturnSelf(); $event->expects($this->once()) ->method('setAuthor') ->with($this->equalTo('jos')) ->willReturnSelf(); $event->expects($this->once()) ->method('setAffectedUser') ->with($this->equalTo('jos')) ->willReturnSelf(); $this->fakeProvider ->method('getDisplayName') ->willReturn('Fake 2FA'); $event->expects($this->once()) ->method('setSubject') ->with($this->equalTo('twofactor_success'), $this->equalTo([ 'provider' => 'Fake 2FA', ])) ->willReturnSelf(); $token = $this->createMock(OC\Authentication\Token\IToken::class); $this->tokenProvider->method('getToken') ->with('mysessionid') ->willReturn($token); $token->method('getId') ->willReturn(42); $this->config->expects($this->once()) ->method('deleteUserValue') ->with('jos', 'login_token_2fa', '42'); $result = $this->manager->verifyChallenge('email', $this->user, $challenge); $this->assertTrue($result); } public function testVerifyChallengeInvalidProviderId(): void { $this->prepareProviders(); $challenge = 'passme'; $this->fakeProvider->expects($this->never()) ->method('verifyChallenge') ->with($this->user, $challenge); $this->session->expects($this->never()) ->method('remove'); $this->assertFalse($this->manager->verifyChallenge('dontexist', $this->user, $challenge)); } public function testVerifyInvalidChallenge(): void { $this->prepareProviders(); $challenge = 'dontpassme'; $event = $this->createMock(IEvent::class); $this->fakeProvider->expects($this->once()) ->method('verifyChallenge') ->with($this->user, $challenge) ->willReturn(false); $this->session->expects($this->never()) ->method('remove'); $this->activityManager->expects($this->once()) ->method('generateEvent') ->willReturn($event); $this->user->expects($this->any()) ->method('getUID') ->willReturn('jos'); $event->expects($this->once()) ->method('setApp') ->with($this->equalTo('core')) ->willReturnSelf(); $event->expects($this->once()) ->method('setType') ->with($this->equalTo('security')) ->willReturnSelf(); $event->expects($this->once()) ->method('setAuthor') ->with($this->equalTo('jos')) ->willReturnSelf(); $event->expects($this->once()) ->method('setAffectedUser') ->with($this->equalTo('jos')) ->willReturnSelf(); $this->fakeProvider ->method('getDisplayName') ->willReturn('Fake 2FA'); $event->expects($this->once()) ->method('setSubject') ->with($this->equalTo('twofactor_failed'), $this->equalTo([ 'provider' => 'Fake 2FA', ])) ->willReturnSelf(); $this->assertFalse($this->manager->verifyChallenge('email', $this->user, $challenge)); } public function testNeedsSecondFactor(): void { $user = $this->createMock(IUser::class); $this->session->expects($this->exactly(3)) ->method('exists') ->withConsecutive( ['app_password'], ['two_factor_auth_uid'], [Manager::SESSION_UID_DONE], ) ->willReturn(false); $this->session->method('getId') ->willReturn('mysessionid'); $token = $this->createMock(OC\Authentication\Token\IToken::class); $this->tokenProvider->method('getToken') ->with('mysessionid') ->willReturn($token); $token->method('getId') ->willReturn(42); $user->method('getUID') ->willReturn('user'); $this->config->method('getUserKeys') ->with('user', 'login_token_2fa') ->willReturn([ '42' ]); $manager = $this->getMockBuilder(Manager::class) ->setConstructorArgs([ $this->providerLoader, $this->providerRegistry, $this->mandatoryTwoFactor, $this->session, $this->config, $this->activityManager, $this->logger, $this->tokenProvider, $this->timeFactory, $this->dispatcher, ]) ->setMethods(['loadTwoFactorApp', 'isTwoFactorAuthenticated'])// Do not actually load the apps ->getMock(); $manager->method('isTwoFactorAuthenticated') ->with($user) ->willReturn(true); $this->assertTrue($manager->needsSecondFactor($user)); } public function testNeedsSecondFactorUserIsNull(): void { $user = null; $this->session->expects($this->never()) ->method('exists'); $this->assertFalse($this->manager->needsSecondFactor($user)); } public function testNeedsSecondFactorWithNoProviderAvailableAnymore(): void { $this->prepareNoProviders(); $user = null; $this->session->expects($this->never()) ->method('exists') ->with('two_factor_auth_uid') ->willReturn(true); $this->session->expects($this->never()) ->method('remove') ->with('two_factor_auth_uid'); $this->assertFalse($this->manager->needsSecondFactor($user)); } public function testPrepareTwoFactorLogin(): void { $this->user->method('getUID') ->willReturn('ferdinand'); $this->session->expects($this->exactly(2)) ->method('set') ->withConsecutive( ['two_factor_auth_uid', 'ferdinand'], ['two_factor_remember_login', true] ); $this->session->method('getId') ->willReturn('mysessionid'); $token = $this->createMock(OC\Authentication\Token\IToken::class); $this->tokenProvider->method('getToken') ->with('mysessionid') ->willReturn($token); $token->method('getId') ->willReturn(42); $this->timeFactory->method('getTime') ->willReturn(1337); $this->config->method('setUserValue') ->with('ferdinand', 'login_token_2fa', '42', '1337'); $this->manager->prepareTwoFactorLogin($this->user, true); } public function testPrepareTwoFactorLoginDontRemember(): void { $this->user->method('getUID') ->willReturn('ferdinand'); $this->session->expects($this->exactly(2)) ->method('set') ->withConsecutive( ['two_factor_auth_uid', 'ferdinand'], ['two_factor_remember_login', false] ); $this->session->method('getId') ->willReturn('mysessionid'); $token = $this->createMock(OC\Authentication\Token\IToken::class); $this->tokenProvider->method('getToken') ->with('mysessionid') ->willReturn($token); $token->method('getId') ->willReturn(42); $this->timeFactory->method('getTime') ->willReturn(1337); $this->config->method('setUserValue') ->with('ferdinand', 'login_token_2fa', '42', '1337'); $this->manager->prepareTwoFactorLogin($this->user, false); } public function testNeedsSecondFactorSessionAuth(): void { $user = $this->createMock(IUser::class); $user->method('getUID') ->willReturn('user'); $this->session->method('exists') ->willReturnCallback(function ($var) { if ($var === Manager::SESSION_UID_KEY) { return false; } elseif ($var === 'app_password') { return false; } elseif ($var === 'app_api') { return false; } return true; }); $this->session->method('get') ->willReturnCallback(function ($var) { if ($var === Manager::SESSION_UID_KEY) { return 'user'; } elseif ($var === 'app_api') { return true; } return null; }); $this->session->expects($this->once()) ->method('get') ->willReturnMap([ [Manager::SESSION_UID_DONE, 'user'], ['app_api', true] ]); $this->assertFalse($this->manager->needsSecondFactor($user)); } public function testNeedsSecondFactorSessionAuthFailDBPass(): void { $user = $this->createMock(IUser::class); $user->method('getUID') ->willReturn('user'); $this->session->method('exists') ->willReturn(false); $this->session->method('getId') ->willReturn('mysessionid'); $token = $this->createMock(OC\Authentication\Token\IToken::class); $token->method('getId') ->willReturn(40); $this->tokenProvider->method('getToken') ->with('mysessionid') ->willReturn($token); $this->config->method('getUserKeys') ->with('user', 'login_token_2fa') ->willReturn([ '42', '43', '44' ]); $this->session->expects($this->once()) ->method('set') ->with(Manager::SESSION_UID_DONE, 'user'); $this->assertFalse($this->manager->needsSecondFactor($user)); } public function testNeedsSecondFactorInvalidToken(): void { $this->prepareNoProviders(); $user = $this->createMock(IUser::class); $user->method('getUID') ->willReturn('user'); $this->session->method('exists') ->willReturn(false); $this->session->method('getId') ->willReturn('mysessionid'); $this->tokenProvider->method('getToken') ->with('mysessionid') ->willThrowException(new OC\Authentication\Exceptions\InvalidTokenException()); $this->config->method('getUserKeys')->willReturn([]); $this->assertFalse($this->manager->needsSecondFactor($user)); } public function testNeedsSecondFactorAppPassword(): void { $user = $this->createMock(IUser::class); $this->session->method('exists') ->willReturnMap([ ['app_password', true], ['app_api', true] ]); $this->assertFalse($this->manager->needsSecondFactor($user)); } public function testClearTwoFactorPending() { $this->config->method('getUserKeys') ->with('theUserId', 'login_token_2fa') ->willReturn([ '42', '43', '44' ]); $this->config->expects($this->exactly(3)) ->method('deleteUserValue') ->withConsecutive( ['theUserId', 'login_token_2fa', '42'], ['theUserId', 'login_token_2fa', '43'], ['theUserId', 'login_token_2fa', '44'], ); $this->tokenProvider->expects($this->exactly(3)) ->method('invalidateTokenById') ->withConsecutive( ['theUserId', 42], ['theUserId', 43], ['theUserId', 44], ); $this->manager->clearTwoFactorPending('theUserId'); } public function testClearTwoFactorPendingTokenDoesNotExist() { $this->config->method('getUserKeys') ->with('theUserId', 'login_token_2fa') ->willReturn([ '42', '43', '44' ]); $this->config->expects($this->exactly(3)) ->method('deleteUserValue') ->withConsecutive( ['theUserId', 'login_token_2fa', '42'], ['theUserId', 'login_token_2fa', '43'], ['theUserId', 'login_token_2fa', '44'], ); $this->tokenProvider->expects($this->exactly(3)) ->method('invalidateTokenById') ->withConsecutive( ['theUserId', 42], ['theUserId', 43], ['theUserId', 44], ) ->willReturnCallback(function ($user, $tokenId) { if ($tokenId === 43) { throw new DoesNotExistException('token does not exist'); } }); $this->manager->clearTwoFactorPending('theUserId'); } } light .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/*
 * SonarScanner CLI
 * Copyright (C) 2011-2022 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonarsource.scanner.cli;

import java.util.Map;
import java.util.Properties;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.sonar.api.utils.MessageException;
import org.sonarsource.scanner.api.EmbeddedScanner;
import org.sonarsource.scanner.api.ScanProperties;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class MainTest {

  @Mock
  private Exit exit;
  @Mock
  private Cli cli;
  @Mock
  private Conf conf;
  @Mock
  private Properties properties;
  @Mock
  private ScannerFactory scannerFactory;
  @Mock
  private EmbeddedScanner scanner;
  @Mock
  private Logs logs;

  @Before
  public void setUp() {
    MockitoAnnotations.initMocks(this);
    when(scannerFactory.create(any(Properties.class), any(String.class))).thenReturn(scanner);
    when(conf.properties()).thenReturn(properties);
  }

  @Test
  public void should_execute_runner() {
    when(cli.getInvokedFrom()).thenReturn("");
    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();

    verify(exit).exit(Exit.SUCCESS);
    verify(scannerFactory).create(properties, "");

    verify(scanner, times(1)).start();
    verify(scanner, times(1)).execute((Map) properties);
  }

  @Test
  public void should_exit_with_error_on_error_during_analysis() {
    EmbeddedScanner runner = mock(EmbeddedScanner.class);
    Exception e = new NullPointerException("NPE");
    e = new IllegalStateException("Error", e);
    doThrow(e).when(runner).execute(any());
    when(cli.getInvokedFrom()).thenReturn("");
    when(scannerFactory.create(any(Properties.class), any(String.class))).thenReturn(runner);
    when(cli.isDebugEnabled()).thenReturn(true);
    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();

    verify(exit).exit(Exit.INTERNAL_ERROR);
    verify(logs).error("Error during SonarScanner execution", e);
  }

  @Test
  public void should_exit_with_error_on_error_during_start() {
    EmbeddedScanner runner = mock(EmbeddedScanner.class);
    Exception e = new NullPointerException("NPE");
    e = new IllegalStateException("Error", e);
    doThrow(e).when(runner).start();
    when(cli.getInvokedFrom()).thenReturn("");
    when(cli.isDebugEnabled()).thenReturn(true);
    when(scannerFactory.create(any(Properties.class), any(String.class))).thenReturn(runner);

    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();

    verify(runner).start();
    verify(runner, never()).execute(any());
    verify(exit).exit(Exit.INTERNAL_ERROR);
    verify(logs).error("Error during SonarScanner execution", e);
  }

  @Test
  public void show_stacktrace() {
    Exception e = createException(false);
    testException(e, false, false, Exit.INTERNAL_ERROR);

    verify(logs).error("Error during SonarScanner execution", e);
    verify(logs).error("Re-run SonarScanner using the -X switch to enable full debug logging.");
  }

  @Test
  public void dont_show_MessageException_stacktrace() {
    Exception e = createException(true);
    testException(e, false, false, Exit.USER_ERROR);

    verify(logs, times(5)).error(anyString());
    verify(logs).error("Error during SonarScanner execution");
    verify(logs).error("my message");
    verify(logs).error("Caused by: A functional cause");
    verify(logs).error("");
    verify(logs).error("Re-run SonarScanner using the -X switch to enable full debug logging.");
  }

  @Test
  public void dont_show_MessageException_stacktrace_embedded() {
    Exception e = createException(true);
    testException(e, false, true, Exit.USER_ERROR);

    verify(logs, times(4)).error(anyString());
    verify(logs).error("Error during SonarScanner execution");
    verify(logs).error("my message");
    verify(logs).error("Caused by: A functional cause");
    verify(logs).error("");
  }

  @Test
  public void show_MessageException_stacktrace_in_debug() {
    Exception e = createException(true);
    testException(e, true, false, Exit.USER_ERROR);

    verify(logs, times(1)).error(anyString(), any(Throwable.class));
    verify(logs).error("Error during SonarScanner execution", e);
  }

  @Test
  public void show_MessageException_stacktrace_in_debug_embedded() {
    Exception e = createException(true);
    testException(e, true, true, Exit.USER_ERROR);

    verify(logs, times(1)).error(anyString(), any(Throwable.class));
    verify(logs).error("Error during SonarScanner execution", e);
  }

  @Test
  public void show_stacktrace_in_debug() {
    Exception e = createException(false);
    testException(e, true, false, Exit.INTERNAL_ERROR);

    verify(logs).error("Error during SonarScanner execution", e);
    verify(logs, never()).error("Re-run SonarScanner using the -X switch to enable full debug logging.");
  }

  private void testException(Exception e, boolean debugEnabled, boolean isEmbedded, int expectedExitCode) {
    when(cli.isDebugEnabled()).thenReturn(debugEnabled);
    when(cli.isEmbedded()).thenReturn(isEmbedded);
    when(cli.getInvokedFrom()).thenReturn("");

    EmbeddedScanner runner = mock(EmbeddedScanner.class);
    doThrow(e).when(runner).execute(any());

    when(scannerFactory.create(any(Properties.class), any(String.class))).thenReturn(runner);

    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();

    verify(exit).exit(expectedExitCode);
  }

  private Exception createException(boolean messageException) {
    Exception e;
    if (messageException) {
      e = new MessageException("my message", new IllegalStateException("A functional cause"));
    } else {
      e = new IllegalStateException("Error", new NullPointerException("NPE"));
    }

    return e;
  }

  @Test
  public void should_only_display_version() {
    Properties p = new Properties();
    when(cli.isDisplayVersionOnly()).thenReturn(true);
    when(cli.getInvokedFrom()).thenReturn("");
    when(conf.properties()).thenReturn(p);

    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();

    InOrder inOrder = Mockito.inOrder(exit, scannerFactory);

    inOrder.verify(exit, times(1)).exit(Exit.SUCCESS);
    inOrder.verify(scannerFactory, times(1)).create(p, "");
    inOrder.verify(exit, times(1)).exit(Exit.SUCCESS);
  }

  @Test
  public void should_skip() {
    Properties p = new Properties();
    p.setProperty(ScanProperties.SKIP, "true");
    when(conf.properties()).thenReturn(p);
    when(cli.getInvokedFrom()).thenReturn("");

    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();

    verify(logs).info("SonarScanner analysis skipped");
    InOrder inOrder = Mockito.inOrder(exit, scannerFactory);

    inOrder.verify(exit, times(1)).exit(Exit.SUCCESS);
    inOrder.verify(scannerFactory, times(1)).create(p, "");
    inOrder.verify(exit, times(1)).exit(Exit.SUCCESS);
  }

  @Test
  public void shouldLogServerVersion() {
    when(scanner.serverVersion()).thenReturn("5.5");
    Properties p = new Properties();
    when(cli.isDisplayVersionOnly()).thenReturn(true);
    when(cli.getInvokedFrom()).thenReturn("");
    when(conf.properties()).thenReturn(p);

    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();
    verify(logs).info("Analyzing on SonarQube server 5.5");
  }

  @Test
  public void should_log_SonarCloud_server() {
    Properties p = new Properties();
    p.setProperty("sonar.host.url", "https://sonarcloud.io");
    when(conf.properties()).thenReturn(p);
    when(cli.getInvokedFrom()).thenReturn("");

    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();
    verify(logs).info("Analyzing on SonarCloud");
  }

  // SQSCANNER-57
  @Test
  public void should_return_true_is_sonar_cloud() {

    Properties properties = new Properties();
    properties.setProperty("sonar.host.url", "https://sonarcloud.io");

    assertThat(Main.isSonarCloud(properties)).isTrue();
  }

  // SQSCANNER-57
  @Test
  public void should_return_false_is_sonar_cloud() {
    Properties properties = new Properties();
    properties.setProperty("sonar.host.url", "https://mysonarqube.com:9000/");

    assertThat(Main.isSonarCloud(properties)).isFalse();
  }

  // SQSCANNER-57
  @Test
  public void should_return_false_is_sonar_cloud_host_is_null() {
    assertThat(Main.isSonarCloud(new Properties())).isFalse();
  }

  @Test
  public void should_configure_logging() {
    Properties analysisProps = testLogging("sonar.verbose", "true");
    assertThat(analysisProps.getProperty("sonar.verbose")).isEqualTo("true");
  }

  @Test
  public void should_configure_logging_trace() {
    Properties analysisProps = testLogging("sonar.log.level", "TRACE");
    assertThat(analysisProps.getProperty("sonar.log.level")).isEqualTo("TRACE");
  }

  @Test
  public void should_configure_logging_debug() {
    Properties analysisProps = testLogging("sonar.log.level", "DEBUG");
    assertThat(analysisProps.getProperty("sonar.log.level")).isEqualTo("DEBUG");
  }

  private Properties testLogging(String propKey, String propValue) {
    Properties p = new Properties();
    p.put(propKey, propValue);
    when(conf.properties()).thenReturn(p);
    when(cli.getInvokedFrom()).thenReturn("");

    Main main = new Main(exit, cli, conf, scannerFactory, logs);
    main.execute();

    // Logger used for callback should have debug enabled
    verify(logs).setDebugEnabled(true);

    ArgumentCaptor<Properties> propertiesCapture = ArgumentCaptor.forClass(Properties.class);
    verify(scanner).execute((Map) propertiesCapture.capture());

    return propertiesCapture.getValue();
  }

}