public void WorkItemFiler_ValidatesSarifLogFileLocationArgument()
        {
            SarifWorkItemFiler filer = CreateWorkItemFiler();

            Action action = () => filer.FileWorkItems(sarifLogFileLocation: null);

            action.Should().Throw <ArgumentNullException>();
        }
        private static FilingClient CreateGitHubMocksAndFilingClient(string bugUriText, string bugHtmlUriText, SarifWorkItemFiler filer)
        {
            FilingClient filingClient;
            Issue        testGithubIssue = new Issue(bugUriText, bugHtmlUriText, bugUriText + "comments", bugUriText + "events",
                                                     111111, ItemState.Open, "TestTitle", "TestBody", new User(), new User(), null, new User(), null, new Milestone(1), 0, new PullRequest(), null,
                                                     new DateTimeOffset(DateTime.Now), null, 111111, null, false, null, null);

            var gitHubClientWrapperMock = new Mock <IGitHubClientWrapper>();

            gitHubClientWrapperMock
            .Setup(x => x.CreateWorkItemAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <NewIssue>()))
            .ReturnsAsync(testGithubIssue)
            .Callback <string, string, NewIssue>(
                (org, repository, issue) =>
            {
                CreateWorkItemCalled++;
            });

            gitHubClientWrapperMock
            .Setup(x => x.UpdateWorkItemAsync(It.IsAny <string>(), It.IsAny <string>(), It.IsAny <int>(), It.IsAny <IssueUpdate>()))
            .ReturnsAsync(testGithubIssue)
            .Callback <string, string, int, IssueUpdate>(
                (org, repository, issueNumber, issue) =>
            {
                UpdateIssueCount++;
            });

            var gitHubConnectionMock = new Mock <IGitHubConnection>();

            gitHubConnectionMock
            .Setup(x => x.ConnectAsync(It.IsAny <string>(), It.IsAny <string>()))
            .ReturnsAsync(gitHubClientWrapperMock.Object)
            .Callback <string, string>(
                (org, pat) =>
            {
                ConnectCalled = true;
                pat.Should().Be(TestData.NotActuallyASecret);
                GitHubFilingUri.OriginalString.Contains(org.ToString()).Should().BeTrue();
            });

            filingClient = (GitHubFilingClient)filer.FilingClient;
            ((GitHubFilingClient)filingClient)._gitHubConnection = gitHubConnectionMock.Object;
            return(filingClient);
        }
        private void TestWorkItemFiler(SarifLog sarifLog, SarifWorkItemContext context, bool adoClient)
        {
            // ONE. Create test data that the low-level ADO client mocks
            //      will flow back to the SARIF work item filer.
            var attachmentReference = new AttachmentReference()
            {
                Id  = Guid.NewGuid(),
                Url = Guid.NewGuid().ToString()
            };

            var workItem = new WorkItem
            {
                Id    = DateTime.UtcNow.Millisecond,
                Links = new ReferenceLinks()
            };

            // The fake URI to the filed item that we'll expect the filer to receive
            string bugUriText     = "https://example.com/" + Guid.NewGuid().ToString();
            string bugHtmlUriText = "https://example.com/" + Guid.NewGuid().ToString();

            Uri bugUri     = new Uri(bugUriText, UriKind.RelativeOrAbsolute);
            Uri bugHtmlUri = new Uri(bugHtmlUriText, UriKind.RelativeOrAbsolute);

            workItem.Url = bugUriText;
            workItem.Links.AddLink("html", bugHtmlUriText);

            // TWO. Reset variables to capture whether we enter all expected client methods.
            ConnectCalled        = false;
            CreateWorkItemCalled = CreateAttachmentCount = UpdateIssueCount = 0;

            // THREE. Create a default mock SARIF filer and client.
            SarifWorkItemFiler filer = CreateMockSarifWorkItemFiler(context).Object;

            // FOUR. Based on which client we are using (ADO or GitHub), create the correct context.
            //       This implies created both the connection mocks and the mocks for filing, updating, and attaching work items.
            //       We are required to put this mock behind an interface due to an inability to mock these types directly.

            FilingClient filingClient;

            if (adoClient == true)
            {
                filingClient = CreateAdoMocksAndFilingClient(attachmentReference, workItem, filer);
            }
            else
            {
                filingClient = CreateGitHubMocksAndFilingClient(bugUriText, bugHtmlUriText, filer);
            }

            string   sarifLogText    = JsonConvert.SerializeObject(sarifLog);
            SarifLog updatedSarifLog = filer.FileWorkItems(sarifLogText);

            // Did we see all the execution we expected?
            ConnectCalled.Should().BeTrue();

            int expectedWorkItemsCount = context.GetProperty(ExpectedWorkItemsCount);

            CreateWorkItemCalled.Should().Be(expectedWorkItemsCount);
            CreateAttachmentCount.Should().Be(adoClient ? expectedWorkItemsCount : 0);

            // This property is a naive mechanism to ensure that the code
            // executed comprehensively (i.e., that execution was not limited
            // due to unhandled exceptions). This is required because we have
            // not really implemented a proper async API with appropriate handling
            // for exceptions and other negative conditions. I wouldn't expect this
            // little helper to survive but it closes the loop for the current
            // rudimentary in-flight implementation.
            filer.FilingResult.Should().Be(FilingResult.Succeeded);

            filer.FiledWorkItems.Count.Should().Be(expectedWorkItemsCount);

            foreach (WorkItemModel filedWorkItem in filer.FiledWorkItems)
            {
                // Finally, make sure that our test data flows back properly through the filer.

                filedWorkItem.Attachment.Should().NotBeNull();
                JsonConvert.SerializeObject(filedWorkItem.Attachment.Text).Should().NotBeNull();

                filedWorkItem.Uri.Should().Be(bugUri);
                filedWorkItem.HtmlUri.Should().Be(bugHtmlUri);
            }

            // Validate that we updated the SARIF log with work itme URIs.
            //
            updatedSarifLog.Should().NotBeEquivalentTo(sarifLog);

            foreach (Run run in updatedSarifLog.Runs)
            {
                foreach (Result result in run.Results)
                {
                    result.WorkItemUris.Should().NotBeNull();
                    result.WorkItemUris.Count.Should().Be(1);
                    result.WorkItemUris[0].Should().Be(bugHtmlUri);

                    result.TryGetProperty(SarifWorkItemFiler.PROGRAMMABLE_URIS_PROPERTY_NAME, out List <Uri> programmableUris)
                    .Should().BeTrue();

                    programmableUris.Should().NotBeNull();
                    programmableUris.Count.Should().Be(1);
                    programmableUris[0].Should().Be(bugUri);
                }
            }
        }
        private static FilingClient CreateAdoMocksAndFilingClient(AttachmentReference attachmentReference, WorkItem workItem, SarifWorkItemFiler filer)
        {
            FilingClient filingClient;
            var          workItemTrackingHttpClientMock = new Mock <IWorkItemTrackingHttpClient>();

            workItemTrackingHttpClientMock
            .Setup(x => x.CreateAttachmentAsync(It.IsAny <MemoryStream>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <object>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(attachmentReference)     // Return our test attachment, defined above.
            .Callback <MemoryStream, string, string, string, object, CancellationToken>(
                (stream, fileName, uploadType, areaPath, userState, cancellationToken) =>
            {
                // Verify that the ADO client receives the request to create an attachment
                CreateAttachmentCount++;
            });

            workItemTrackingHttpClientMock
            .Setup(x => x.CreateWorkItemAsync(It.IsAny <JsonPatchDocument>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <bool?>(), It.IsAny <bool?>(), It.IsAny <bool?>(), It.IsAny <object>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(workItem)     // Return our test work item, defined above
            .Callback <JsonPatchDocument, string, string, bool?, bool?, bool?, object, CancellationToken>(
                (document, project, type, validateOnly, bypassRules, suppressNotifications, userState, cancellationToken) =>
            {
                // Verify that the ADO client receives the request to file the bug
                CreateWorkItemCalled++;
            });

            var vssConnectionMock = new Mock <IVssConnection>();

            vssConnectionMock
            .Setup(x => x.ConnectAsync(It.IsAny <Uri>(), It.IsAny <string>()))
            .Returns(Task.CompletedTask)
            .Callback <Uri, string>(
                (uri, pat) =>
            {
                // The following data values flow to us via test constants which are
                // captured in the default context object that initialized the filer.
                pat.Should().Be(TestData.NotActuallyASecret);

                // We configure the filer with a full ADO URI that contains the account and
                // project, e.g., https://dev.azure.com/myaccount/myproject. By the time the
                // ADO client receives it, however, the project has been stripped off
                // (because it is not required for making the connection).
                AzureDevOpsFilingUri.OriginalString.StartsWith(uri.ToString()).Should().BeTrue();

                // Verify that we received the connection request.
                ConnectCalled = true;
            });

            // Our GetClientAsync is overridden to provide our low-level ADO mock.
            vssConnectionMock
            .Setup(x => x.GetClientAsync())
            .ReturnsAsync(workItemTrackingHttpClientMock.Object);

            // FIVE. We are required to inject the low level ADO connection wrapper. We are
            //       required to do so due to the complexity of creating and initializing
            //       required objects. MOQ cannot override constructors and (related)
            //       in general cannot instantiate a type without a parameterless ctor.
            //       Even when a concrete class can be instantiated, its various properties
            //       might not be easily stubbed (as for a non-virtual property accessor).
            //       For these cases, we need to insert an interface in our system, and
            //       create wrappers around those concrete types, where the interface is
            //       directly mapped or otherwise adapted to the concrete type's methods.
            //       Moq can then simply manufacture a class that implements that interface
            //       in order to control behaviors.
            //
            //       Rather than introducing a type factory or more sophisticated pattern
            //       of injection, we use the very simple expedient of declaring an internal
            //       property to hold a mock instance. In production, if that property is null,
            //       the system instantiates a wrapper around the standard types for use.

            filingClient = (AzureDevOpsFilingClient)filer.FilingClient;
            ((AzureDevOpsFilingClient)filingClient)._vssConection = vssConnectionMock.Object;
            return(filingClient);
        }
Exemple #5
0
        private void TestWorkItemFiler(SarifLog sarifLog, SarifWorkItemContext context)
        {
            // ONE. Create test data that the low-level ADO client mocks
            //      will flow back to the SARIF work item filer.
            var attachmentReference = new AttachmentReference()
            {
                Id  = Guid.NewGuid(),
                Url = Guid.NewGuid().ToString()
            };

            var workItem = new WorkItem
            {
                Id    = DateTime.UtcNow.Millisecond,
                Links = new ReferenceLinks()
            };

            // The fake URI to the filed item that we'll expect the filer to receive
            string bugUriText     = "https://example.com/" + Guid.NewGuid().ToString();
            string bugHtmlUriText = "https://example.com/" + Guid.NewGuid().ToString();

            Uri bugUri     = new Uri(bugUriText, UriKind.RelativeOrAbsolute);
            Uri bugHtmlUri = new Uri(bugHtmlUriText, UriKind.RelativeOrAbsolute);

            workItem.Url = bugUriText;
            workItem.Links.AddLink("html", bugHtmlUriText);

            // TWO. Define variables to capture whether we enter all expected ADO client methods.
            bool connectCalled = false;
            int  createWorkItemCalled = 0, createAttachmentCalled = 0;

            // THREE. Create a default mock SARIF filer and client, configured by an AzureDevOps context
            //        (which creates a default ADO filing client underneath).
            SarifWorkItemFiler filer = CreateMockSarifWorkItemFiler(context).Object;

            var workItemTrackingHttpClientMock = new Mock <IWorkItemTrackingHttpClient>();

            workItemTrackingHttpClientMock
            .Setup(x => x.CreateAttachmentAsync(It.IsAny <MemoryStream>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <object>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(attachmentReference)     // Return our test attachment, defined above.
            .Callback <MemoryStream, string, string, string, object, CancellationToken>(
                (stream, fileName, uploadType, areaPath, userState, cancellationToken) =>
            {
                // Verify that the ADO client receives the request to create an attachment
                createAttachmentCalled++;
            });

            workItemTrackingHttpClientMock
            .Setup(x => x.CreateWorkItemAsync(It.IsAny <JsonPatchDocument>(), It.IsAny <string>(), It.IsAny <string>(), It.IsAny <bool?>(), It.IsAny <bool?>(), It.IsAny <bool?>(), It.IsAny <object>(), It.IsAny <CancellationToken>()))
            .ReturnsAsync(workItem)     // Return our test work item, defined above
            .Callback <JsonPatchDocument, string, string, bool?, bool?, bool?, object, CancellationToken>(
                (document, project, type, validateOnly, bypassRules, suppressNotifications, userState, cancellationToken) =>
            {
                // Verify that the ADO client receives the request to file the bug
                createWorkItemCalled++;
            });

            // FOUR. Create a mock VssConnection instance, which handles the auth connection
            //       and retrieval of the ADO filing client. We are required to put this
            //       mock behind an interface due to an inability

            var vssConnectionMock = new Mock <IVssConnection>();

            vssConnectionMock
            .Setup(x => x.ConnectAsync(It.IsAny <Uri>(), It.IsAny <string>()))
            .Returns(Task.CompletedTask)
            .Callback <Uri, string>(
                (uri, pat) =>
            {
                // The following data values flow to us via test constants which are
                // captured in the default context object that initialized the filer.
                pat.Should().Be(TestData.NotActuallyASecret);

                // We configure the filer with a full ADO URI that contains the account and
                // project, e.g., https://dev.azure.com/myaccount/myproject. By the time the
                // ADO client receives it, however, the project has been stripped off
                // (because it is not required for making the connection).
                AzureDevOpsFilingUri.OriginalString.StartsWith(uri.ToString()).Should().BeTrue();

                // Verify that we received the connection request.
                connectCalled = true;
            });

            // Our GetClientAsync is overridden to provide our low-level ADO mock.
            vssConnectionMock
            .Setup(x => x.GetClientAsync())
            .ReturnsAsync(workItemTrackingHttpClientMock.Object);

            // FIVE. We are required to inject the low level ADO connection wrapper. We are
            //       required to do so due to the complexity of creating and initializing
            //       required objects. MOQ cannot override constructors and (related)
            //       in general cannot instantiate a type without a parameterless ctor.
            //       Even when a concrete class can be instantiated, its various properties
            //       might not be easily stubbed (as for a non-virtual property accessor).
            //       For these cases, we need to insert an interface in our system, and
            //       create wrappers around those concrete types, where the interface is
            //       directly mapped or otherwise adapted to the concrete type's methods.
            //       Moq can then simply manufacture a class that implements that interface
            //       in order to control behaviors.
            //
            //       Rather than introducing a type factory or more sophisticated pattern
            //       of injection, we use the very simple expedient of declaring an internal
            //       property to hold a mock instance. In production, if that property is null,
            //       the system instantiates a wrapper around the standard types for use.

            var adoFilingClient = (AzureDevOpsFilingClient)filer.FilingClient;

            adoFilingClient._vssConection = vssConnectionMock.Object;


            string   sarifLogText    = JsonConvert.SerializeObject(sarifLog);
            SarifLog updatedSarifLog = filer.FileWorkItems(sarifLogText);

            // Did we see all the execution we expected?
            connectCalled.Should().BeTrue();

            int expectedWorkItemsCount = context.GetProperty(ExpectedWorkItemsCount);

            createWorkItemCalled.Should().Be(expectedWorkItemsCount);
            createAttachmentCalled.Should().Be(expectedWorkItemsCount);

            // This property is a naive mechanism to ensure that the code
            // executed comprehensively (i.e., that execution was not limited
            // due to unhandled exceptions). This is required because we have
            // not really implemented a proper async API with appropriate handling
            // for exceptions and other negative conditions. I wouldn't expect this
            // little helper to survive but it closes the loop for the current
            // rudimentary in-flight implementation.
            filer.FilingResult.Should().Be(FilingResult.Succeeded);

            filer.FiledWorkItems.Count.Should().Be(expectedWorkItemsCount);

            foreach (WorkItemModel filedWorkItem in filer.FiledWorkItems)
            {
                // Finally, make sure that our test data flows back properly through the filer.

                filedWorkItem.Attachment.Should().NotBeNull();
                JsonConvert.SerializeObject(filedWorkItem.Attachment.Text).Should().NotBeNull();

                filedWorkItem.Uri.Should().Be(bugUri);
                filedWorkItem.HtmlUri.Should().Be(bugHtmlUri);
            }

            // Validate that we updated the SARIF log with work itme URIs.
            //
            updatedSarifLog.Should().NotBeEquivalentTo(sarifLog);

            foreach (Run run in updatedSarifLog.Runs)
            {
                foreach (Result result in run.Results)
                {
                    result.WorkItemUris.Should().NotBeNull();
                    result.WorkItemUris.Count.Should().Be(1);
                    result.WorkItemUris[0].Should().Be(bugHtmlUri);

                    result.TryGetProperty(SarifWorkItemFiler.PROGRAMMABLE_URIS_PROPERTY_NAME, out List <Uri> programmableUris)
                    .Should().BeTrue();

                    programmableUris.Should().NotBeNull();
                    programmableUris.Count.Should().Be(1);
                    programmableUris[0].Should().Be(bugUri);
                }
            }
        }