public void UpdateConnection_Disconnect_WpfCommandIsAvailableButNotExecutable_ServiceDisconnectedIsCalled()
        {
            // If the WPF command is available then it should be used to disconnect,
            // rather than calling service.Disconnect() directly.
            // Here, the command exists but is not executable.

            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            int commandCallCount           = 0;
            int commandCanExecuteCallCount = 0;
            var teSection = ConfigurableSectionController.CreateDefault();

            teSection.DisconnectCommand = new Integration.WPF.RelayCommand(
                () => commandCallCount++,
                () => { commandCanExecuteCallCount++; return(false); });
            host.SetActiveSection(teSection);

            ConfigureService(isConnected: true);
            ConfigureSolutionBinding(null);

            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: false);

            // Assert
            VerifyServiceConnect(Times.Never());
            VerifyServiceDisconnect(Times.Once());
            commandCanExecuteCallCount.Should().Be(1);
            commandCallCount.Should().Be(0);
        }
        public void OnBindingStateChanged_NewConfiguration_EventRaised()
        {
            // Arrange
            var initialProject = new BoundSonarQubeProject(
                new Uri("http://localhost:9000"),
                "projectKey",
                organization: new SonarQubeOrganization("myOrgKey", "myOrgName"));

            // Set the current configuration used by the tracker
            ConfigureSolutionBinding(initialProject);
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            int solutionBindingChangedEventCount = 0;

            testSubject.SolutionBindingChanged += (obj, args) => { solutionBindingChangedEventCount++; };

            // Now configure the provider to return a different configuration
            var newProject = new BoundSonarQubeProject(
                new Uri("http://localhost:9000"),
                "projectKey",
                organization: new SonarQubeOrganization("myOrgKey_DIFFERENT", "myOrgName"));

            ConfigureSolutionBinding(newProject);

            // Act - simulate the binding state changing in the Team explorer section.
            // The project configuration hasn't changed (it doesn't matter what properties
            // we pass here; they aren't used when raising the event.)
            host.VisualStateManager.SetBoundProject(new Uri("http://junk"), "any", "any");

            // Assert
            // Different config so event should be raised
            solutionBindingChangedEventCount.Should().Be(1);
        }
        public void UpdateConnection_Disconnect_WpfCommandIsAvailableAndExecutable_ServiceDisconnectedIsNotCalled()
        {
            // Wpf command is available and can be executed -> should be called instead of service.Disconnect()

            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            int commandCallCount           = 0;
            int commandCanExecuteCallCount = 0;
            var teSection = ConfigurableSectionController.CreateDefault();

            teSection.DisconnectCommand = new Integration.WPF.RelayCommand(
                () => { commandCallCount++; isMockServiceConnected = false; },
                () => { commandCanExecuteCallCount++; return(true); });
            host.SetActiveSection(teSection);

            ConfigureService(isConnected: true);
            ConfigureSolutionBinding(null);

            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: false);

            // Assert
            VerifyServiceConnect(Times.Never());
            VerifyServiceDisconnect(Times.Never());
            commandCanExecuteCallCount.Should().Be(1);
            commandCallCount.Should().Be(1);
        }
        public void ActiveSolutionBoundTracker_Unbound()
        {
            // Arrange
            host.VisualStateManager.ClearBoundProject();

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeFalse("Unbound solution should report false activation");
            this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
        }
        public void ActiveSolutionBoundTracker_Unbound()
        {
            // Setup
            host.VisualStateManager.ClearBoundProject();

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);

            // Verify
            Assert.IsFalse(testSubject.IsActiveSolutionBound, "Unbound solution should report false activation");
            this.errorListController.AssertRefreshCalled(1);
            this.errorListController.AssertResetCalled(0);
        }
        public void ActiveSolutionBoundTracker_Unbound()
        {
            // Setup
            host.VisualStateManager.ClearBoundProject();

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);

            // Verify
            Assert.IsFalse(testSubject.IsActiveSolutionBound, "Unbound solution should report false activation");
            this.errorListController.AssertRefreshCalled(1);
            this.errorListController.AssertResetCalled(0);
        }
        public void ActiveSolutionBoundTracker_Initialisation_Bound()
        {
            // Arrange
            this.ConfigureSolutionBinding(new BoundSonarQubeProject());

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            // Assert
            testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.LegacyConnected, "Bound solution should report true activation");
            this.errorListController.RefreshCalledCount.Should().Be(0);
            this.errorListController.ResetCalledCount.Should().Be(0);
        }
        public void ActiveSolutionBoundTracker_Initialisation_Unbound()
        {
            // Arrange
            host.VisualStateManager.ClearBoundProject();

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            // Assert
            testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.Standalone, "Unbound solution should report false activation");
            this.errorListController.RefreshCalledCount.Should().Be(0);
            this.errorListController.ResetCalledCount.Should().Be(0);
        }
        public void ActiveSolutionBoundTracker_Bound()
        {
            // Arrange
            this.solutionBindingInformationProvider.SolutionBound = true;
            this.host.VisualStateManager.SetBoundProject(new ProjectInformation());

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeTrue("Bound solution should report true activation");
            this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
        }
        public void ActiveSolutionBoundTracker_Bound()
        {
            // Setup
            this.solutionBindingInformationProvider.SolutionBound = true;
            this.host.VisualStateManager.SetBoundProject(new ProjectInformation());

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);

            // Verify
            Assert.IsTrue(testSubject.IsActiveSolutionBound, "Bound solution should report true activation");
            this.errorListController.AssertRefreshCalled(1);
            this.errorListController.AssertResetCalled(0);
        }
        public void ActiveSolutionBoundTracker_Bound()
        {
            // Setup
            this.solutionBindingInformationProvider.SolutionBound = true;
            this.host.VisualStateManager.SetBoundProject(new ProjectInformation());

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);

            // Verify
            Assert.IsTrue(testSubject.IsActiveSolutionBound, "Bound solution should report true activation");
            this.errorListController.AssertRefreshCalled(1);
            this.errorListController.AssertResetCalled(0);
        }
        public void ActiveSolutionBoundTracker_Initialisation_Bound()
        {
            // Arrange
            this.solutionBindingInformationProvider.SolutionBound = true;
            this.host.VisualStateManager.SetBoundProject(new SonarQubeProject("", ""));

            // Act
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, sonarLintOutputMock.Object);

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeTrue("Bound solution should report true activation");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
        }
        public void SolutionBindingUpdated_WhenClearBoundProject_NotRaised()
        {
            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);
            int callCount   = 0;

            testSubject.SolutionBindingUpdated += (sender, e) => callCount++;

            // Act
            host.VisualStateManager.ClearBoundProject();

            // Assert
            callCount.Should().Be(0);
        }
        public void SolutionBindingUpdated_WhenSetBoundProject_Raised()
        {
            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);
            int callCount   = 0;

            testSubject.SolutionBindingUpdated += (sender, e) => callCount++;

            // Act
            host.VisualStateManager.SetBoundProject(new Uri("http://localhost"), null, "project123");

            // Assert
            callCount.Should().Be(1);
        }
        public void UpdateConnection_WasConnected_NewSolutionIsUnbound_DisconnectedCalled()
        {
            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            ConfigureService(isConnected: true);
            ConfigureSolutionBinding(null);

            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true);

            // Assert
            VerifyServiceConnect(Times.Never());
            VerifyServiceDisconnect(Times.Once());
            isMockServiceConnected.Should().Be(false);
        }
        public void UpdateConnection_WasDisconnected_NewSolutionIsBound_ConnectCalled()
        {
            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            ConfigureService(isConnected: false);
            ConfigureSolutionBinding(new BoundSonarQubeProject(new Uri("http://foo"), "projectKey"));

            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true);

            // Assert
            VerifyServiceConnect(Times.Once());
            VerifyServiceDisconnect(Times.Never());
            isMockServiceConnected.Should().Be(true);
        }
        public void UpdateConnection_WasDisconnected_NewSolutionIsUnbound_NoConnectOrDisconnectCalls()
        {
            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, sonarLintOutputMock.Object);

            ConfigureService(isConnected: false);
            ConfigureSolutionBinding(null);

            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Assert
            VerifyServiceConnect(Times.Never());
            VerifyServiceDisconnect(Times.Never());
            isMockServiceConnected.Should().Be(false);
        }
        public void UpdateConnection_WasConnected_NewSolutionBound_ConnectedCalled()
        {
            // Note: we don't expect this to happen in practice - we should only
            // ever go from connected->disconnected, or disconnected->connected,
            // never connected->connected. However, we should handle it gracefully
            // just in case.

            // Arrange
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            ConfigureService(isConnected: true);
            ConfigureSolutionBinding(new BoundSonarQubeProject(new Uri("http://foo"), "projectKey"));

            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true);

            // Assert
            VerifyServiceConnect(Times.Once());
            VerifyServiceDisconnect(Times.Once());
            isMockServiceConnected.Should().Be(true);
        }
        public void OnBindingStateChanged_SameConfiguration_EventNotRaised()
        {
            // Arrange
            var boundProject = new BoundSonarQubeProject(
                new Uri("http://localhost:9000"),
                "projectKey",
                organization: new SonarQubeOrganization("myOrgKey", "myOrgName"));

            // Set the current configuration used by the tracker
            ConfigureSolutionBinding(boundProject);
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);

            int solutionBindingChangedEventCount = 0;

            testSubject.SolutionBindingChanged += (obj, args) => { solutionBindingChangedEventCount++; };

            // Act - simulate the binding state changing in the Team explorer section.
            host.VisualStateManager.SetBoundProject(new Uri("http://junk"), "any", "any");

            // Assert
            // Same config so event should not be raised
            solutionBindingChangedEventCount.Should().Be(0);
        }
        public void ActiveSolutionBoundTracker_When_ConnectAsync_Throws_Write_To_Output()
        {
            // Arrange
            var sonarQubeServiceMock = new Mock <ISonarQubeService>();

            this.host.SonarQubeService = sonarQubeServiceMock.Object;

            var activeSolutionBoundTracker = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker,
                                                                            this.loggerMock.Object);

            // We want to directly jump to Connect
            sonarQubeServiceMock.SetupGet(x => x.IsConnected).Returns(false);
            ConfigureSolutionBinding(new BoundSonarQubeProject(new Uri("http://test"), "projectkey"));

            // ConnectAsync should throw
            sonarQubeServiceMock
            .SetupSequence(x => x.ConnectAsync(It.IsAny <ConnectionInformation>(), It.IsAny <CancellationToken>()))
            .Throws <Exception>()
            .Throws <TaskCanceledException>()
            .Throws(new HttpRequestException("http request", new Exception("something happened")));

            // Act
            // Throwing errors will put the connection and binding out of sync, which
            // cause a Debug.Assert in the product code that we need to suppress
            using (new AssertIgnoreScope())
            {
                this.activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true);
                this.activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: false);
                this.activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true);
            }

            // Assert
            this.loggerMock
            .Verify(x => x.WriteLine(It.Is <string>(s => s.StartsWith("SonarQube request failed:")), It.IsAny <object[]>()), Times.Exactly(2));
            this.loggerMock
            .Verify(x => x.WriteLine(It.Is <string>(s => s.StartsWith("SonarQube request timed out or was canceled"))), Times.Once);
        }
        public void ActiveSolutionBoundTracker_Changes()
        {
            var solutionBinding = new ConfigurableSolutionBindingSerializer
            {
                CurrentBinding = new BoundSonarQubeProject()
            };
            this.serviceProvider.RegisterService(typeof(ISolutionBindingSerializer), solutionBinding);
            this.solutionBindingInformationProvider.SolutionBound = true;
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);
            var reanalysisEventCalledCount = 0;
            testSubject.SolutionBindingChanged += (obj, args) => { reanalysisEventCalledCount++; };

            // Sanity
            Assert.IsTrue(testSubject.IsActiveSolutionBound, "Initially bound");
            this.errorListController.AssertRefreshCalled(1);
            this.errorListController.AssertResetCalled(0);
            Assert.AreEqual(0, reanalysisEventCalledCount, "No reanalysis forced");

            // Case 1: Clear bound project
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = false;
            // Act
            host.VisualStateManager.ClearBoundProject();

            // Verify
            Assert.IsFalse(testSubject.IsActiveSolutionBound, "Unbound solution should report false activation");
            this.errorListController.AssertRefreshCalled(1);
            this.errorListController.AssertResetCalled(0);
            Assert.AreEqual(1, reanalysisEventCalledCount, "Unbind should trigger reanalysis");

            // Case 2: Set bound project
            solutionBinding.CurrentBinding = new BoundSonarQubeProject();
            this.solutionBindingInformationProvider.SolutionBound = true;
            // Act
            host.VisualStateManager.SetBoundProject(new ProjectInformation());

            // Verify
            Assert.IsTrue(testSubject.IsActiveSolutionBound, "Bound solution should report true activation");
            this.errorListController.AssertRefreshCalled(1);
            this.errorListController.AssertResetCalled(0);
            Assert.AreEqual(2, reanalysisEventCalledCount, "Bind should trigger reanalysis");

            // Case 3: Solution unloaded
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = false;
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Verify
            Assert.IsFalse(testSubject.IsActiveSolutionBound, "Should respond to solution change event and report unbound");
            this.errorListController.AssertRefreshCalled(2);
            this.errorListController.AssertResetCalled(0);
            Assert.AreEqual(3, reanalysisEventCalledCount, "Solution change should trigger reanalysis");

            // Case 4: Solution loaded
            solutionBinding.CurrentBinding = new BoundSonarQubeProject();
            this.solutionBindingInformationProvider.SolutionBound = true;
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Verify
            Assert.IsTrue(testSubject.IsActiveSolutionBound, "Bound respond to solution change event and report bound");
            this.errorListController.AssertRefreshCalled(3);
            this.errorListController.AssertResetCalled(0);
            Assert.AreEqual(4, reanalysisEventCalledCount, "Solution change should trigger reanalysis");

            // Case 5: Dispose and change
            // Act
            testSubject.Dispose();
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = true;
            host.VisualStateManager.ClearBoundProject();

            // Verify
            Assert.AreEqual(4, reanalysisEventCalledCount, "Once disposed should stop raising the event");
            this.errorListController.AssertRefreshCalled(3);
            this.errorListController.AssertResetCalled(1);
        }
        public void ActiveSolutionBoundTracker_Changes()
        {
            var boundProject = new BoundSonarQubeProject(new Uri("http://localhost:9000"), "key");

            var solutionBinding = new ConfigurableSolutionBindingSerializer();

            bool serviceIsConnected = false;
            var  mockSqService      = new Mock <ISonarQubeService>();

            Expression <Func <ISonarQubeService, Task> > connectMethod    = x => x.ConnectAsync(It.IsAny <ConnectionInformation>(), It.IsAny <CancellationToken>());
            Expression <Action <ISonarQubeService> >     disconnectMethod = x => x.Disconnect();

            mockSqService.SetupGet(x => x.IsConnected).Returns(() => serviceIsConnected);
            mockSqService.Setup(disconnectMethod).Callback(() => serviceIsConnected = false).Verifiable();
            mockSqService.Setup(connectMethod)
            .Returns(Task.Delay(0))
            .Callback(() => serviceIsConnected = true).Verifiable();
            this.host.SonarQubeService         = mockSqService.Object;

            this.serviceProvider.RegisterService(typeof(ISolutionBindingSerializer), solutionBinding);

            solutionBinding.CurrentBinding = boundProject;
            this.solutionBindingInformationProvider.SolutionBound = true;
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);
            var solutionBindingChangedEventCount = 0;

            testSubject.SolutionBindingChanged += (obj, args) => { solutionBindingChangedEventCount++; };

            // Sanity
            testSubject.IsActiveSolutionBound.Should().BeTrue("Initially bound");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(0, "no events raised during construction");

            // Case 1: Clear bound project
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = false;
            // Act
            host.VisualStateManager.ClearBoundProject();

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeFalse("Unbound solution should report false activation");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(1, "Unbind should trigger reanalysis");

            // Connect not called
            // Disconnect not called
            mockSqService.Verify(disconnectMethod, Times.Never);
            mockSqService.Verify(connectMethod, Times.Never);

            // Case 2: Set bound project
            solutionBinding.CurrentBinding = boundProject;
            this.solutionBindingInformationProvider.SolutionBound = true;
            // Act
            host.VisualStateManager.SetBoundProject(new SonarQubeProject("", ""));

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeTrue("Bound solution should report true activation");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(2, "Bind should trigger reanalysis");

            // Notifications from the Team Explorer should not trigger connect/disconnect
            mockSqService.Verify(disconnectMethod, Times.Never);
            mockSqService.Verify(connectMethod, Times.Never);

            // Case 3: Solution unloaded
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = false;
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeFalse("Should respond to solution change event and report unbound");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(2);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(3, "Solution change should trigger reanalysis");

            // Closing an unbound solution should not call disconnect/connect
            mockSqService.Verify(disconnectMethod, Times.Never);
            mockSqService.Verify(connectMethod, Times.Never);

            // Case 4: Load a bound solution
            solutionBinding.CurrentBinding = boundProject;
            this.solutionBindingInformationProvider.SolutionBound = true;
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeTrue("Bound respond to solution change event and report bound");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(3);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(4, "Solution change should trigger reanalysis");

            // Loading a bound solution should call connect
            mockSqService.Verify(disconnectMethod, Times.Never);
            mockSqService.Verify(connectMethod, Times.Exactly(1));

            // Case 5: Close a bound solution
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = false;
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Assert
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(4);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(5, "Solution change should trigger reanalysis");

            // Closing a bound solution should call disconnect
            mockSqService.Verify(disconnectMethod, Times.Exactly(1));
            mockSqService.Verify(connectMethod, Times.Exactly(1));

            // Case 6: Dispose and change
            // Act
            testSubject.Dispose();
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = true;
            host.VisualStateManager.ClearBoundProject();

            // Assert
            solutionBindingChangedEventCount.Should().Be(5, "Once disposed should stop raising the event");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(3);
            this.errorListController.ResetCalledCount.Should().Be(1);
            mockSqService.Verify(disconnectMethod, Times.Exactly(1));
            mockSqService.Verify(connectMethod, Times.Exactly(1));
        }
        public void ActiveSolutionBoundTracker_Changes()
        {
            var solutionBinding = new ConfigurableSolutionBindingSerializer
            {
                CurrentBinding = new BoundSonarQubeProject()
            };

            this.serviceProvider.RegisterService(typeof(ISolutionBindingSerializer), solutionBinding);
            this.solutionBindingInformationProvider.SolutionBound = true;
            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker);
            var reanalysisEventCalledCount = 0;

            testSubject.SolutionBindingChanged += (obj, args) => { reanalysisEventCalledCount++; };

            // Sanity
            testSubject.IsActiveSolutionBound.Should().BeTrue("Initially bound");
            this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
            reanalysisEventCalledCount.Should().Be(0, "No reanalysis forced");

            // Case 1: Clear bound project
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = false;
            // Act
            host.VisualStateManager.ClearBoundProject();

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeFalse("Unbound solution should report false activation");
            this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
            reanalysisEventCalledCount.Should().Be(1, "Unbind should trigger reanalysis");

            // Case 2: Set bound project
            solutionBinding.CurrentBinding = new BoundSonarQubeProject();
            this.solutionBindingInformationProvider.SolutionBound = true;
            // Act
            host.VisualStateManager.SetBoundProject(new ProjectInformation());

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeTrue("Bound solution should report true activation");
            this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
            reanalysisEventCalledCount.Should().Be(2, "Bind should trigger reanalysis");

            // Case 3: Solution unloaded
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = false;
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeFalse("Should respond to solution change event and report unbound");
            this.errorListController.RefreshCalledCount.Should().Be(2);
            this.errorListController.ResetCalledCount.Should().Be(0);
            reanalysisEventCalledCount.Should().Be(3, "Solution change should trigger reanalysis");

            // Case 4: Solution loaded
            solutionBinding.CurrentBinding = new BoundSonarQubeProject();
            this.solutionBindingInformationProvider.SolutionBound = true;
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged();

            // Assert
            testSubject.IsActiveSolutionBound.Should().BeTrue("Bound respond to solution change event and report bound");
            this.errorListController.RefreshCalledCount.Should().Be(3);
            this.errorListController.ResetCalledCount.Should().Be(0);
            reanalysisEventCalledCount.Should().Be(4, "Solution change should trigger reanalysis");

            // Case 5: Dispose and change
            // Act
            testSubject.Dispose();
            solutionBinding.CurrentBinding = null;
            this.solutionBindingInformationProvider.SolutionBound = true;
            host.VisualStateManager.ClearBoundProject();

            // Assert
            reanalysisEventCalledCount.Should().Be(4, "Once disposed should stop raising the event");
            this.errorListController.RefreshCalledCount.Should().Be(3);
            this.errorListController.ResetCalledCount.Should().Be(1);
        }
        public void ActiveSolutionBoundTracker_Changes()
        {
            var boundProject = new BoundSonarQubeProject(new Uri("http://localhost:9000"), "key");

            ConfigureService(isConnected: false);
            ConfigureSolutionBinding(boundProject);

            var testSubject = new ActiveSolutionBoundTracker(this.host, this.activeSolutionTracker, loggerMock.Object);
            var solutionBindingChangedEventCount = 0;

            testSubject.SolutionBindingChanged += (obj, args) => { solutionBindingChangedEventCount++; };

            // Sanity
            testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.LegacyConnected, "Initially bound");
            this.errorListController.RefreshCalledCount.Should().Be(0);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(0, "no events raised during construction");

            // Case 1: Clear bound project
            ConfigureSolutionBinding(null);
            // Act
            host.VisualStateManager.ClearBoundProject();

            // Assert
            testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.Standalone, "Unbound solution should report false activation");
            this.errorListController.RefreshCalledCount.Should().Be(0);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(1, "Unbind should trigger reanalysis");

            VerifyServiceDisconnect(Times.Never());
            VerifyServiceConnect(Times.Never());

            // Case 2: Set bound project
            ConfigureSolutionBinding(boundProject);
            // Act
            host.VisualStateManager.SetBoundProject(new Uri("http://localhost"), null, "project123");

            // Assert
            testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.LegacyConnected, "Bound solution should report true activation");
            this.errorListController.RefreshCalledCount.Should().Be(0);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(2, "Bind should trigger reanalysis");

            // Notifications from the Team Explorer should not trigger connect/disconnect
            VerifyServiceDisconnect(Times.Never());
            VerifyServiceConnect(Times.Never());

            // Case 3: Bound solution unloaded -> disconnect
            ConfigureSolutionBinding(null);
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: false);

            // Assert
            testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.Standalone, "Should respond to solution change event and report unbound");
            this.errorListController.RefreshCalledCount.Should().Be(1);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(3, "Solution change should trigger reanalysis");

            // Closing an unbound solution should not call disconnect/connect
            VerifyServiceDisconnect(Times.Never());
            VerifyServiceConnect(Times.Never());

            // Case 4: Load a bound solution
            ConfigureSolutionBinding(boundProject);
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: true);

            // Assert
            testSubject.CurrentConfiguration.Mode.Should().Be(SonarLintMode.LegacyConnected, "Bound respond to solution change event and report bound");
            this.errorListController.RefreshCalledCount.Should().Be(2);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(4, "Solution change should trigger reanalysis");

            // Loading a bound solution should call connect
            VerifyServiceDisconnect(Times.Never());
            VerifyServiceConnect(Times.Once());

            // Case 5: Close a bound solution
            ConfigureSolutionBinding(null);
            // Act
            activeSolutionTracker.SimulateActiveSolutionChanged(isSolutionOpen: false);

            // Assert
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(4);
            this.errorListController.ResetCalledCount.Should().Be(0);
            solutionBindingChangedEventCount.Should().Be(5, "Solution change should trigger reanalysis");

            // SonarQubeService.Disconnect should be called since the WPF DisconnectCommand is not available
            VerifyServiceDisconnect(Times.Once());
            VerifyServiceConnect(Times.Once());

            // Case 6: Dispose and change
            // Act
            testSubject.Dispose();
            ConfigureSolutionBinding(boundProject);
            host.VisualStateManager.ClearBoundProject();

            // Assert
            solutionBindingChangedEventCount.Should().Be(5, "Once disposed should stop raising the event");
            // TODO: this.errorListController.RefreshCalledCount.Should().Be(3);
            this.errorListController.ResetCalledCount.Should().Be(1);
            // SonarQubeService.Disconnect should be called since the WPF DisconnectCommand is not available
            VerifyServiceDisconnect(Times.Once());
            VerifyServiceConnect(Times.Once());
        }