Represents the connection information needed to connect to SonarQube service
Inheritance: ICloneable, IDisposable
        public BindingWorkflow(IHost host, ConnectionInformation connectionInformation, ProjectInformation project)
        {
            if (host == null)
            {
                throw new ArgumentNullException(nameof(host));
            }

            if (connectionInformation == null)
            {
                throw new ArgumentNullException(nameof(connectionInformation));
            }

            if (project == null)
            {
                throw new ArgumentNullException(nameof(project));
            }

            this.host = host;
            this.connectionInformation = connectionInformation;
            this.project = project;
            this.projectSystem = this.host.GetService<IProjectSystemHelper>();
            this.projectSystem.AssertLocalServiceIsNotNull();

            this.solutionBindingOperation = new SolutionBindingOperation(
                    this.host,
                    this.connectionInformation,
                    this.project.Key);
        }
        public SolutionBindingOperation(IServiceProvider serviceProvider, ConnectionInformation connection, string sonarQubeProjectKey)
        {
            if (serviceProvider == null)
            {
                throw new ArgumentNullException(nameof(serviceProvider));
            }

            if (connection == null)
            {
                throw new ArgumentNullException(nameof(connection));
            }

            if (string.IsNullOrWhiteSpace(sonarQubeProjectKey))
            {
                throw new ArgumentNullException(nameof(sonarQubeProjectKey));
            }

            this.serviceProvider = serviceProvider;
            this.connection = connection;
            this.sonarQubeProjectKey = sonarQubeProjectKey;

            this.projectSystem = this.serviceProvider.GetService<IProjectSystemHelper>();
            this.projectSystem.AssertLocalServiceIsNotNull();

            this.sourceControlledFileSystem = this.serviceProvider.GetService<ISourceControlledFileSystem>();
            this.sourceControlledFileSystem.AssertLocalServiceIsNotNull();
        }
        public void LatestServer_APICompatibility()
        {
            // Setup the service used to interact with SQ
            var s = new SonarQubeServiceWrapper(this.serviceProvider);
            var connection = new ConnectionInformation(new Uri("http://nemo.sonarqube.org"));

            // Step 1: Connect anonymously
            ProjectInformation[] projects = null;

            RetryAction(() => s.TryGetProjects(connection, CancellationToken.None, out projects),
                                        "Get projects from SonarQube server");
            Assert.AreNotEqual(0, projects.Length, "No projects were returned");

            // Step 2: Get quality profile for the first project
            var project = projects.FirstOrDefault();
            QualityProfile profile = null;
            RetryAction(() => s.TryGetQualityProfile(connection, project, Language.CSharp, CancellationToken.None, out profile),
                                        "Get quality profile from SonarQube server");
            Assert.IsNotNull(profile, "No quality profile was returned");

            // Step 3: Get quality profile export for the quality profile
            RoslynExportProfile export = null;
            RetryAction(() => s.TryGetExportProfile(connection, profile, Language.CSharp, CancellationToken.None, out export),
                                        "Get quality profile export from SonarQube server");
            Assert.IsNotNull(export, "No quality profile export was returned");

            // Errors are logged to output window pane and we don't expect any
            this.outputWindowPane.AssertOutputStrings(0);
        }
        public void ConnectionWorkflow_ConnectionStep_SuccessfulConnection()
        {
            // Setup
            var connectionInfo = new ConnectionInformation(new Uri("http://server"));
            var projects = new ProjectInformation[] { new ProjectInformation { Key = "project1" } };
            this.sonarQubeService.ReturnProjectInformation = projects;
            bool projectChangedCallbackCalled = false;
            this.host.TestStateManager.SetProjectsAction = (c, p) =>
            {
                projectChangedCallbackCalled = true;
                Assert.AreSame(connectionInfo, c, "Unexpected connection");
                CollectionAssert.AreEqual(projects, p.ToArray(), "Unexpected projects");
            };
            
            var controller = new ConfigurableProgressController();
            var executionEvents = new ConfigurableProgressStepExecutionEvents();
            string connectionMessage = connectionInfo.ServerUri.ToString();
            var testSubject= new ConnectionWorkflow(this.host, new RelayCommand(AssertIfCalled));

            // Act
            testSubject.ConnectionStep(controller, CancellationToken.None, connectionInfo, executionEvents);

            // Verify
            executionEvents.AssertProgressMessages(connectionMessage, Strings.ConnectionResultSuccess);
            Assert.IsTrue(projectChangedCallbackCalled, "ConnectedProjectsCallaback was not called");
            sonarQubeService.AssertConnectRequests(1);
            Assert.AreEqual(connectionInfo, testSubject.ConnectedServer);
            ((ConfigurableUserNotification)this.host.ActiveSection.UserNotifications).AssertNoShowErrorMessages();
            ((ConfigurableUserNotification)this.host.ActiveSection.UserNotifications).AssertNoNotification(NotificationIds.FailedToConnectId);
        }
        public void ServerViewModel_SetProjects()
        {
            // Setup
            var connInfo = new ConnectionInformation(new Uri("https://myawesomeserver:1234/"));
            var viewModel = new ServerViewModel(connInfo);
            IEnumerable<ProjectInformation> projects = new[]
            {
                new ProjectInformation { Name = "Project3", Key="1" },
                new ProjectInformation { Name = "Project2", Key="2" },
                new ProjectInformation { Name = "project1", Key="3" },
            };
            string[] expectedOrderedProjectNames = projects.Select(p => p.Name).OrderBy(n => n, StringComparer.CurrentCulture).ToArray();

            // Act
            viewModel.SetProjects(projects);

            // Verify
            string[] actualProjectNames = viewModel.Projects.Select(p => p.ProjectInformation.Name).OrderBy(n => n, StringComparer.CurrentCulture).ToArray();
            CollectionAssert.AreEqual(
               expectedOrderedProjectNames,
               actualProjectNames,
               message: $"VM projects [{string.Join(", ", actualProjectNames)}] do not match the expected projects [{string.Join(", ", expectedOrderedProjectNames)}]"
           );

            // Act again
            var newProject = new ProjectInformation();
            viewModel.SetProjects(new[] { newProject });

            // Verify that the collection was replaced with the new one
            Assert.AreSame(newProject, viewModel.Projects.SingleOrDefault()?.ProjectInformation, "Expected a single project to be present");
        }
        internal /* testing purposes */ static ConnectionInformation CreateConnectionInformation(ConnectionInfoDialogViewModel viewModel, SecureString password)
        {
            if (viewModel == null)
            {
                throw new ArgumentNullException(nameof(viewModel));
            }

            if (password == null)
            {
                throw new ArgumentNullException(nameof(password));
            }

            ConnectionInformation info = null;

            Debug.Assert(viewModel.IsValid, "View model should be valid when creating connection information");
            if (viewModel.IsValid)
            {
                Uri serverUri = viewModel.ServerUrl;
                string username = viewModel.Username;

                info = new ConnectionInformation(serverUri, username, password);
            }

            return info;
        }
        public void ConnectionInformation_WithLoginInformation()
        {
            // Setup
            var userName = "******";
            var passwordUnsecure = "admin";
            var password = passwordUnsecure.ConvertToSecureString();
            var serverUri = new Uri("http://localhost/");
            var testSubject = new ConnectionInformation(serverUri, userName, password);

            // Act
            password.Dispose(); // Connection information should maintain it's own copy of the password

            // Verify
            Assert.AreEqual(passwordUnsecure, testSubject.Password.ConvertToUnsecureString(), "Password doesn't match");
            Assert.AreEqual(userName, testSubject.UserName, "UserName doesn't match");
            Assert.AreEqual(serverUri, testSubject.ServerUri, "ServerUri doesn't match");

            // Act clone
            var testSubject2 = (ConnectionInformation)((ICloneable)testSubject).Clone();

            // Now dispose the test subject
            testSubject.Dispose();

            // Verify testSubject
            Exceptions.Expect<ObjectDisposedException>(() => testSubject.Password.ConvertToUnsecureString());

            // Verify testSubject2
            Assert.AreEqual(passwordUnsecure, testSubject2.Password.ConvertToUnsecureString(), "Password doesn't match");
            Assert.AreEqual(userName, testSubject2.UserName, "UserName doesn't match");
            Assert.AreEqual(serverUri, testSubject2.ServerUri, "ServerUri doesn't match");
        }
 ConnectionInformation IConnectionInformationProvider.GetConnectionInformation(ConnectionInformation currentConnection)
 {
     if (this.ExpectExistingConnection)
     {
         Assert.IsNotNull(currentConnection, "No existing connection provided");
     }
     return this.ConnectionInformationToReturn;
 }
        public void ConnectionInformation_Ctor_NormalizesServerUri()
        {
            // Act
            var noSlashResult = new ConnectionInformation(new Uri("http://localhost/NoSlash"));

            // Verify
            Assert.AreEqual("http://localhost/NoSlash/", noSlashResult.ServerUri.ToString(), "Unexpected normalization of URI without trailing slash");
        }
        public void ServerViewModel_Ctor_NullArgumentChecks()
        {
            var connInfo = new ConnectionInformation(new Uri("http://localhost"));

            Exceptions.Expect<ArgumentNullException>(() =>
            {
                new ServerViewModel(null);
            });
        }
        public IProgressEvents Run(ConnectionInformation information)
        {
            Debug.Assert(this.host.ActiveSection != null, "Expect the section to be attached at least until this method returns");

            this.OnProjectsChanged(information, null);
            IProgressEvents progress = ProgressStepRunner.StartAsync(this.host, this.host.ActiveSection.ProgressHost, (controller) => this.CreateConnectionSteps(controller, information));
            this.DebugOnly_MonitorProgress(progress);
            return progress;
        }
        public ServerViewModel(ConnectionInformation connectionInformation, bool isExpanded = true)
        {
            if (connectionInformation == null)
            {
                throw new ArgumentNullException(nameof(connectionInformation));
            }

            this.connectionInformation = connectionInformation;
            this.IsExpanded = isExpanded;
        }
        public void BindingWorkflow_ArgChecks()
        {
            var validConnection = new ConnectionInformation(new Uri("http://server"));
            var validProjectInfo = new ProjectInformation();
            var validHost = new ConfigurableHost();

            Exceptions.Expect<ArgumentNullException>(() => new BindingWorkflow(null, validConnection, validProjectInfo));
            Exceptions.Expect<ArgumentNullException>(() => new BindingWorkflow(validHost, null, validProjectInfo));
            Exceptions.Expect<ArgumentNullException>(() => new BindingWorkflow(validHost, validConnection, null));
        }
 void IConnectionWorkflowExecutor.EstablishConnection(ConnectionInformation information)
 {
     this.numberOfCalls++;
     Assert.IsNotNull(information, "Should not request to establish to a null connection");
     // Simulate the expected behavior in product
     if (!this.sonarQubeService.TryGetProjects(information, CancellationToken.None, out this.lastConnectedProjects))
     {
         Assert.Fail("Failed to establish connection");
     }
 }
        public void SolutionBindingOperation_ArgChecks()
        {
            var connectionInformation = new ConnectionInformation(new Uri("http://valid"));
            Exceptions.Expect<ArgumentNullException>(() => new SolutionBindingOperation(null, connectionInformation, "key"));
            Exceptions.Expect<ArgumentNullException>(() => new SolutionBindingOperation(this.serviceProvider, null, "key"));
            Exceptions.Expect<ArgumentNullException>(() => new SolutionBindingOperation(this.serviceProvider, connectionInformation, null));
            Exceptions.Expect<ArgumentNullException>(() => new SolutionBindingOperation(this.serviceProvider, connectionInformation, string.Empty));

            var testSubject = new SolutionBindingOperation(this.serviceProvider, connectionInformation, "key");
            Assert.IsNotNull(testSubject, "Avoid 'testSubject' not used analysis warning");
        }
        internal /*for testing purposes*/ static ConnectionInfoDialogViewModel CreateViewModel(ConnectionInformation currentConnection)
        {
            var vm = new ConnectionInfoDialogViewModel();
            if (currentConnection != null)
            {
                vm.ServerUrlRaw = currentConnection.ServerUri.AbsoluteUri;
                vm.Username = currentConnection.UserName;
            }

            return vm;
        }
        public void RegisterProjectDashboardUrl(ConnectionInformation connectionInfo, ProjectInformation projectInfo, Uri url)
        {
            var serverUrl = connectionInfo.ServerUri.ToString();
            var projectKey = projectInfo.Key;
            if (!this.projectDashboardUrls.ContainsKey(serverUrl))
            {
                this.projectDashboardUrls[serverUrl] = new Dictionary<string, Uri>();
            }

            this.projectDashboardUrls[serverUrl][projectKey] = url;
        }
        private ProgressStepDefinition[] CreateConnectionSteps(IProgressController controller, ConnectionInformation connection)
        {
            string connectStepDisplayText = string.Format(CultureInfo.CurrentCulture, Resources.Strings.ConnectingToSever, connection.ServerUri);
            return new[]
            {
                    new ProgressStepDefinition(connectStepDisplayText, StepAttributes.Indeterminate | StepAttributes.BackgroundThread,
                        (cancellationToken, notifications) => this.ConnectionStep(controller, cancellationToken, connection, notifications)),

                    new ProgressStepDefinition(connectStepDisplayText, StepAttributes.BackgroundThread,
                        (token, notifications) => this.DownloadServiceParameters(controller, token, notifications)),

                };
        }
 public static AuthenticationHeaderValue GetAuthenticationHeader(ConnectionInformation connectionInfo)
 {
     if (connectionInfo.Authentication == AuthenticationType.Basic)
     {
         return string.IsNullOrWhiteSpace(connectionInfo.UserName)
             ? null
             : new AuthenticationHeaderValue("Basic", GetBasicAuthToken(connectionInfo.UserName, connectionInfo.Password));
         // See more info: https://www.visualstudio.com/en-us/integrate/get-started/auth/overview
     }
     else
     {
         Debug.Fail("Unsupported Authentication: " + connectionInfo.Authentication);
         return null;
     }
 }
        /// <summary>
        /// Opens a window and returns only when the dialog is closed.
        /// </summary>
        /// <param name="currentConnection">Optional, the current connection information to show in the dialog</param>
        /// <returns>Captured connection information if closed successfully, null otherwise.</returns>
        public ConnectionInformation ShowDialog(ConnectionInformation currentConnection)
        {
            ConnectionInfoDialogViewModel vm = CreateViewModel(currentConnection);
            ConnectionInfoDialogView dialog = CreateView();
            dialog.ViewModel = vm;
            dialog.Owner = Application.Current.MainWindow;

            bool? result = dialog.ShowDialog();

            if (result.GetValueOrDefault())
            {
                return CreateConnectionInformation(vm, dialog.Password);
            }

            return null;
        }
        public void ServerViewModel_Ctor()
        {
            // Setup
            var connInfo = new ConnectionInformation(new Uri("https://myawesomeserver:1234/"));
            IEnumerable<ProjectInformation> projects = new[]
            {
                new ProjectInformation { Name = "Project1", Key="1" },
                new ProjectInformation { Name = "Project2", Key="2" },
                new ProjectInformation { Name = "Project3", Key="3" },
                new ProjectInformation { Name = "Project4", Key="4" }
            };
            string[] projectKeys = projects.Select(x => x.Key).ToArray();

            // Case 0: default constructed state
            // Act
            var emptyViewModel = new ServerViewModel(connInfo);

            // Verify
            Assert.IsTrue(emptyViewModel.IsExpanded);
            Assert.IsFalse(emptyViewModel.ShowAllProjects);

            // Case 1, projects with default IsExpanded value
            // Act
            var viewModel = new ServerViewModel(connInfo);
            viewModel.SetProjects(projects);

            // Verify
            string[] vmProjectKeys = viewModel.Projects.Select(x => x.Key).ToArray();

            Assert.IsTrue(viewModel.ShowAllProjects);
            Assert.IsTrue(viewModel.IsExpanded);
            Assert.AreEqual(connInfo.ServerUri, viewModel.Url);
            CollectionAssert.AreEqual(
                expected: projectKeys,
                actual: vmProjectKeys,
                message: $"VM projects [{string.Join(", ", vmProjectKeys)}] do not match input projects [{string.Join(", ", projectKeys)}]"
            );

            // Case 2, null projects with non default IsExpanded value
            // Act
            var viewModel2 = new ServerViewModel(connInfo, isExpanded: false);

            // Verify
            Assert.AreEqual(0, viewModel2.Projects.Count, "Not expecting projects");
            Assert.IsFalse(viewModel2.IsExpanded);
        }
        public void ConnectionInformation_WithoutLoginInformation()
        {
            // Setup
            var serverUri = new Uri("http://localhost/");

            // Act
            var testSubject = new ConnectionInformation(serverUri);

            // Verify
            Assert.IsNull(testSubject.Password, "Password wasn't provided");
            Assert.IsNull(testSubject.UserName, "UserName wasn't provided");
            Assert.AreEqual(serverUri, testSubject.ServerUri, "ServerUri doesn't match");

            // Act clone
            var testSubject2 = (ConnectionInformation)((ICloneable)testSubject).Clone();

            // Verify testSubject2
            Assert.IsNull(testSubject2.Password, "Password wasn't provided");
            Assert.IsNull(testSubject2.UserName, "UserName wasn't provided");
            Assert.AreEqual(serverUri, testSubject2.ServerUri, "ServerUri doesn't match");
        }
        private void EstablishConnection(ConnectionInformation connectionInfo)
        {
            Debug.Assert(connectionInfo != null);

            this.LastAttemptedConnection = connectionInfo;

            this.WorkflowExecutor.EstablishConnection(connectionInfo);
        }
 private SolutionBindingOperation CreateTestSubject(string projectKey, ConnectionInformation connection = null)
 {
     return new SolutionBindingOperation(this.serviceProvider,
         connection ?? new ConnectionInformation(new Uri("http://host")),
         projectKey);
 }
        public void SolutionBindingOperation_CommitSolutionBinding()
        {
            // Setup
            this.serviceProvider.RegisterService(typeof(Persistence.ISolutionBindingSerializer), this.solutionBinding);
            var csProject = this.solutionMock.AddOrGetProject("CS.csproj");
            csProject.SetCSProjectKind();
            var projects = new[] { csProject };

            var connectionInformation = new ConnectionInformation(new Uri("http://xyz"));
            SolutionBindingOperation testSubject = this.CreateTestSubject("key", connectionInformation);

            var ruleSetMap = new Dictionary<Language, RuleSet>();
            ruleSetMap[Language.CSharp] = new RuleSet("cs");
            testSubject.RegisterKnownRuleSets(ruleSetMap);
            var profiles = GetQualityProfiles();
            profiles[Language.CSharp] = new QualityProfile { Key = "C# Profile", QualityProfileTimestamp = DateTime.Now };
            testSubject.Initialize(projects, profiles);
            testSubject.Binders.Clear(); // Ignore the real binders, not part of this test scope
            bool commitCalledForBinder = false;
            testSubject.Binders.Add(new ConfigurableBindingOperation { CommitAction = () => commitCalledForBinder = true });
            testSubject.Prepare(CancellationToken.None);
            this.solutionBinding.WriteSolutionBindingAction = bindingInfo =>
            {
                Assert.AreEqual(connectionInformation.ServerUri, bindingInfo.ServerUri);
                Assert.AreEqual(1, bindingInfo.Profiles.Count);

                QualityProfile csProfile = profiles[Language.CSharp];
                Assert.AreEqual(csProfile.Key, bindingInfo.Profiles[Language.CSharp].ProfileKey);
                Assert.AreEqual(csProfile.QualityProfileTimestamp, bindingInfo.Profiles[Language.CSharp].ProfileTimestamp);

                return "Doesn't matter";
            };

            // Sanity
            this.solutionBinding.AssertWrittenFiles(0);

            // Act
            var commitResult = testSubject.CommitSolutionBinding();

            // Verify
            Assert.IsTrue(commitResult);
            Assert.IsTrue(commitCalledForBinder);
            Assert.IsTrue(this.solutionItemsProject.Files.ContainsKey(@"c:\solution\SonarQube\keyCSharp.ruleset"), "Ruleset was expected to be added to solution items");
            this.solutionBinding.AssertWrittenFiles(1);
        }
 private void OnProjectsChanged(ConnectionInformation connection, ProjectInformation[] projects)
 {
     this.host.VisualStateManager.SetProjects(connection, projects);
 }
        private bool VerifyDotNetPlugins(IProgressController controller, CancellationToken cancellationToken, ConnectionInformation connection, IProgressStepExecutionEvents notifications)
        {
            notifications.ProgressChanged(Strings.DetectingServerPlugins);

            ServerPlugin[] plugins;
            if (!this.host.SonarQubeService.TryGetPlugins(connection, cancellationToken, out plugins))
            {
                notifications.ProgressChanged(cancellationToken.IsCancellationRequested ? Strings.ConnectionResultCancellation : Strings.ConnectionResultFailure);
                this.host.ActiveSection?.UserNotifications?.ShowNotificationError(Strings.ConnectionFailed, NotificationIds.FailedToConnectId, this.parentCommand);

                AbortWorkflow(controller, cancellationToken);
                return false;
            }

            IsCSharpPluginSupported = VerifyPluginSupport(plugins, MinimumSupportedServerPlugin.CSharp);
            IsVBNetPluginSupported = VerifyPluginSupport(plugins, MinimumSupportedServerPlugin.VbNet);

            var projects = this.projectSystem.GetSolutionProjects().ToList();
            var anyCSharpProject = projects.Any(p => MinimumSupportedServerPlugin.CSharp.ISupported(p));
            var anyVbNetProject = projects.Any(p => MinimumSupportedServerPlugin.VbNet.ISupported(p));

            string errorMessage;
            if ((IsCSharpPluginSupported && anyCSharpProject) ||
                (IsVBNetPluginSupported && anyVbNetProject))
            {
                return true;
            }
            else if (!IsCSharpPluginSupported && !IsVBNetPluginSupported)
            {
                errorMessage = Strings.ServerHasNoSupportedPluginVersion;
            }
            else if (projects.Count == 0)
            {
                errorMessage = Strings.SolutionContainsNoSupportedProject;
            }
            else if (IsCSharpPluginSupported && !anyCSharpProject)
            {
                errorMessage = string.Format(Strings.OnlySupportedPluginHasNoProjectInSolution, Language.CSharp.Name);
            }
            else
            {
                errorMessage = string.Format(Strings.OnlySupportedPluginHasNoProjectInSolution, Language.VBNET.Name);
            }

            this.host.ActiveSection?.UserNotifications?.ShowNotificationError(errorMessage, NotificationIds.BadServerPluginId, null);
            VsShellUtils.WriteToSonarLintOutputPane(this.host, Strings.SubTextPaddingFormat, errorMessage);
            notifications.ProgressChanged(Strings.ConnectionResultFailure);

            AbortWorkflow(controller, cancellationToken);
            return false;
        }
        internal /* for testing purposes */ void ConnectionStep(IProgressController controller, CancellationToken cancellationToken, ConnectionInformation connection, IProgressStepExecutionEvents notifications)
        {
            this.host.ActiveSection?.UserNotifications?.HideNotification(NotificationIds.FailedToConnectId);
            this.host.ActiveSection?.UserNotifications?.HideNotification(NotificationIds.BadServerPluginId);

            notifications.ProgressChanged(connection.ServerUri.ToString());

            if (!this.VerifyDotNetPlugins(controller, cancellationToken, connection, notifications))
            {
                return;
            }

            this.ConnectedServer = connection;

            ProjectInformation[] projects;
            if (!this.host.SonarQubeService.TryGetProjects(connection, cancellationToken, out projects))
            {
                notifications.ProgressChanged(cancellationToken.IsCancellationRequested ? Strings.ConnectionResultCancellation : Strings.ConnectionResultFailure);
                this.host.ActiveSection?.UserNotifications?.ShowNotificationError(Strings.ConnectionFailed, NotificationIds.FailedToConnectId, this.parentCommand);

                AbortWorkflow(controller, cancellationToken);
                return;
            }

            this.OnProjectsChanged(connection, projects);
            notifications.ProgressChanged(Strings.ConnectionResultSuccess);
        }
 void IConnectionWorkflowExecutor.EstablishConnection(ConnectionInformation information)
 {
     ConnectionWorkflow workflow = new ConnectionWorkflow(this.host, this.ConnectCommand);
     IProgressEvents progressEvents = workflow.Run(information);
     this.SetConnectionInProgress(progressEvents);
 }
 ConnectionInformation IConnectionInformationProvider.GetConnectionInformation(ConnectionInformation currentConnection)
 {
     var dialog = new ConnectionInformationDialog();
     return dialog.ShowDialog(currentConnection);
 }