Esempio n. 1
0
        public async Task Check_Latest_Published_Apk_File_OK()
        {
            BuildTestData();
            var date = DateTime.UtcNow.AddMinutes(-20);
            var modifiedArtifact2_1 = new ProductArtifact
            {
                ProductId      = product1.Id,
                ProductBuildId = productBuild2.Id,
                ArtifactType   = "apk",
                Url            = "https://sil-prd-aps-artifacts.s3.amazonaws.com/prd/jobs/build_scriptureappbuilder_1/2880/test-4.7.apk",
                ContentType    = "application/octet-stream",
                LastModified   = date
            };
            var webRequestWrapper     = _fixture.GetService <WebRequestWrapper>();
            var webRequestWrapperMock = Mock.Get(webRequestWrapper);

            webRequestWrapperMock.Reset();
            webRequestWrapperMock.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a => a.ArtifactType == "apk")))
            .Returns(modifiedArtifact2_1);

            var url      = $"/api/products/{product1.Id}/files/published/apk";
            var response = await Head(url, DateTime.UtcNow.ToUniversalTime().ToString("r"));

            Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        }
        protected async Task <DateTime?> AddProductArtifactAsync(string key, string value, Guid productId, ProductBuild productBuild)
        {
            var productArtifact = new ProductArtifact
            {
                ProductId      = productId,
                ProductBuildId = productBuild.Id,
                ArtifactType   = key,
                Url            = value
            };
            var updatedArtifact = WebRequestWrapper.GetFileInfo(productArtifact);
            await ProductArtifactRepository.CreateAsync(updatedArtifact);

#pragma warning disable RECS0061 // Warns when a culture-aware 'EndsWith' call is used by default.
            if (key == "version" && updatedArtifact.ContentType == "application/json")
#pragma warning restore RECS0061 // Warns when a culture-aware 'EndsWith' call is used by default.
            {
                var contents = WebClient.DownloadString(value);
                var version  = JsonConvert.DeserializeObject <Dictionary <string, string> >(contents);
                if (version.ContainsKey("version"))
                {
                    productBuild.Version = version["version"];
                    await ProductBuildRepository.UpdateAsync(productBuild);
                }
            }

            return(updatedArtifact.LastModified);
        }
        public async Task Get_Build_Get_ConsoleText()
        {
            BuildTestData();
            var buildBuildService     = _fixture.GetService <BuildEngineBuildService>();
            var mockBuildEngine       = Mock.Get(buildBuildService.BuildEngineApi);
            var mockWebRequestWrapper = Mock.Get(buildBuildService.WebRequestWrapper);
            var mockWebClient         = Mock.Get(buildBuildService.WebClient);

            mockBuildEngine.Reset();
            mockWebRequestWrapper.Reset();
            mockWebClient.Reset();

            var productBuild = AddEntity <AppDbContext, ProductBuild>(new ProductBuild
            {
                ProductId = product2.Id,
                BuildId   = product2.WorkflowBuildId
            });
            var buildResponse = new BuildResponse
            {
                Id        = 2,
                JobId     = 1,
                Status    = "completed",
                Result    = "FAILURE",
                Error     = "Error",
                Artifacts = new Dictionary <string, string>()
                {
                    { "consoleText", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7-output.log" }
                }
            };
            var modifiedArtifact1 = new ProductArtifact
            {
                ProductId    = product2.Id,
                ArtifactType = "consoleText",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7-output.log",
                ContentType  = "text/plain",
                FileSize     = 1831,
                LastModified = DateTime.UtcNow
            };


            mockBuildEngine.Setup(x => x.GetBuild(It.IsAny <int>(), It.IsAny <int>())).Returns(buildResponse);

            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "consoleText")))
            .Returns(modifiedArtifact1);

            var consoleText = await buildBuildService.GetConsoleText(product2.Id);

            Assert.Equal("https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7-output.log", consoleText);
        }
        public virtual ProductArtifact GetFileInfo(ProductArtifact artifact)
        {
            var modifiedArtifact = artifact;

            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(artifact.Url);
                request.Method = "HEAD";
                using (HttpWebResponse resp = (HttpWebResponse)(request.GetResponse()))
                {
                    modifiedArtifact.ContentType  = resp.ContentType;
                    modifiedArtifact.LastModified = resp.LastModified.ToUniversalTime();
                    if (resp.ContentType != "text/html")
                    {
                        modifiedArtifact.FileSize = resp.ContentLength;
                    }
                }
            }
            catch (Exception)
            {
            }
            return(modifiedArtifact);
        }
Esempio n. 5
0
        public async Task Build_Check_BuildAsync()
        {
            BuildTestData();
            var buildBuildService     = _fixture.GetService <BuildEngineBuildService>();
            var mockBuildEngine       = Mock.Get(buildBuildService.BuildEngineApi);
            var mockWebRequestWrapper = Mock.Get(buildBuildService.WebRequestWrapper);
            var mockWebClient         = Mock.Get(buildBuildService.WebClient);

            mockBuildEngine.Reset();
            mockWebRequestWrapper.Reset();
            mockWebClient.Reset();
            var modifiedArtifact1 = new ProductArtifact
            {
                ProductId    = product1.Id,
                ArtifactType = "apk",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7.apk",
                ContentType  = "application/octet-stream",
                FileSize     = 8684905,
                LastModified = DateTime.UtcNow
            };
            var modifiedArtifact2 = new ProductArtifact
            {
                ProductId    = product1.Id,
                ArtifactType = "about",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/about.txt",
                ContentType  = "text/plain",
                FileSize     = 1831,
                LastModified = DateTime.UtcNow
            };
            var modifiedArtifact3 = new ProductArtifact
            {
                ProductId    = product1.Id,
                ArtifactType = "version",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/version.json",
                ContentType  = "application/json",
                FileSize     = 1831,
                LastModified = DateTime.UtcNow
            };
            var artifacts = new Dictionary <string, string>()
            {
                { "apk", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7.apk" },
                { "about", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/about.txt" },
                { "version", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/version.json" }
            };

            product2.WorkflowBuildId = 42;

            var productBuild = AddEntity <AppDbContext, ProductBuild>(new ProductBuild
            {
                ProductId = product2.Id,
                BuildId   = 42,
            });


            var buildResponse = new BuildResponse
            {
                Id        = 2,
                JobId     = 1,
                Status    = "completed",
                Result    = "SUCCESS",
                Error     = "",
                Artifacts = artifacts
            };

            mockBuildEngine.Setup(x => x.GetBuild(It.Is <int>(i => i == product2.WorkflowJobId),
                                                  It.Is <int>(b => b == product2.WorkflowBuildId)))
            .Returns(buildResponse);
            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "apk")))
            .Returns(modifiedArtifact1);
            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "about")))
            .Returns(modifiedArtifact2);
            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "version")))
            .Returns(modifiedArtifact3);
            mockWebClient.Setup(x => x.DownloadString(It.Is <string>(addr => addr == modifiedArtifact3.Url)))
            .Returns("{ \"version\" : \"4.7.6\", \"versionName\" : \"4.7\", \"versionCode\" : \"6\" } ");
            await buildBuildService.CheckBuildAsync(product2.Id);

            mockBuildEngine.Verify(x => x.SetEndpoint(
                                       It.Is <String>(u => u == org1.BuildEngineUrl),
                                       It.Is <String>(t => t == org1.BuildEngineApiAccessToken)
                                       ));
            var modifiedArtifacts = ReadTestData <AppDbContext, ProductArtifact>();

            Assert.Equal(3, modifiedArtifacts.Count);
            var modifiedApk = modifiedArtifacts.First(a => a.ArtifactType == modifiedArtifact1.ArtifactType);

            Assert.Equal(modifiedArtifact1.Url, modifiedApk.Url);
            Assert.Equal(modifiedArtifact1.ContentType, modifiedApk.ContentType);
            Assert.Equal(modifiedArtifact1.FileSize, modifiedApk.FileSize);
            var modifiedProductBuilds = ReadTestData <AppDbContext, ProductBuild>();

            Assert.Equal(1, modifiedProductBuilds.Count);
            var build = modifiedProductBuilds.First();

            Assert.Equal("4.7.6", build.Version);
        }
        protected async Task <DateTime?> AddProductArtifactAsync(string key, string value, Product product, ProductBuild productBuild, bool successful)
        {
            var productArtifact = new ProductArtifact
            {
                ProductId      = product.Id,
                ProductBuildId = productBuild.Id,
                ArtifactType   = key,
                Url            = value
            };
            var updatedArtifact  = WebRequestWrapper.GetFileInfo(productArtifact);
            var existingArtifact = await ProductArtifactRepository
                                   .Get().Where(a => a.ProductId == product.Id && a.ProductBuildId == productBuild.Id && a.ArtifactType == key && a.Url == value)
                                   .FirstOrDefaultAsync();

            if (existingArtifact != null)
            {
                // Not sure why we are getting multiple of these, but we don't want multiple entries.
                // Should we ignore it or update?  Ignore for now. Updating threw exceptions
                Log.Information($"Updating Artifact: Id={existingArtifact.Id}");
                updatedArtifact.Id = existingArtifact.Id;
                // await ProductArtifactRepository.UpdateAsync(updatedArtifact);
            }
            else
            {
                var newArtifact = await ProductArtifactRepository.CreateAsync(updatedArtifact);

                Log.Information($"Created Artifact: Id={newArtifact.Id}");
            }

            // On version.json, update the ProductBuild.Version
            if (key == "version" && updatedArtifact.ContentType == "application/json")
            {
                try
                {
                    var contents = WebClient.DownloadString(value);
                    var version  = JsonConvert.DeserializeObject <Dictionary <string, object> >(contents);
                    if (version.ContainsKey("version"))
                    {
                        productBuild.Version = version["version"] as String;
                        await ProductBuildRepository.UpdateAsync(productBuild);

                        if (successful)
                        {
                            product.VersionBuilt = version["version"] as String;
                            await ProductRepository.UpdateAsync(product);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.Error(ex, $"Parsing {key}: {value}");
                }
            }

            // On play-listing-manifest.json, update the Project.DefaultLanguage
            if (key == "play-listing-manifest" && updatedArtifact.ContentType == "application/json")
            {
                try
                {
                    var contents = WebClient.DownloadString(value);
                    var manifest = JsonConvert.DeserializeObject <Dictionary <string, object> >(contents);
                    if (manifest.ContainsKey("default-language"))
                    {
                        var           languageName  = manifest["default-language"] as String;
                        StoreLanguage storeLanguage = await LanguageRepository.Get().Where(lang => lang.Name == languageName).FirstOrDefaultAsync();

                        if (storeLanguage != null)
                        {
                            product.StoreLanguageId = storeLanguage.Id;
                            await ProductRepository.UpdateAsync(product);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Log.Error(ex, $"Parsing {key}: {value}");
                }
            }

            return(updatedArtifact.LastModified);
        }
        public async Task Get_Build_Check_Failure_Default_Build_Engine()
        {
            BuildTestData();
            var buildBuildService     = _fixture.GetService <BuildEngineBuildService>();
            var mockBuildEngine       = Mock.Get(buildBuildService.BuildEngineApi);
            var mockWebRequestWrapper = Mock.Get(buildBuildService.WebRequestWrapper);
            var mockWebClient         = Mock.Get(buildBuildService.WebClient);

            mockBuildEngine.Reset();
            mockWebRequestWrapper.Reset();
            mockWebClient.Reset();

            var productBuild = AddEntity <AppDbContext, ProductBuild>(new ProductBuild
            {
                ProductId = product3.Id,
                BuildId   = product3.WorkflowBuildId
            });
            var buildResponse = new BuildResponse
            {
                Id        = 2,
                JobId     = 1,
                Status    = "completed",
                Result    = "FAILURE",
                Error     = "Error",
                Artifacts = new Dictionary <string, string>()
                {
                    { "consoleText", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7-output.log" }
                }
            };
            var modifiedArtifact1 = new ProductArtifact
            {
                ProductId    = product3.Id,
                ArtifactType = "consoleText",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7-output.log",
                ContentType  = "text/plain",
                FileSize     = 1831,
                LastModified = DateTime.UtcNow
            };


            mockBuildEngine.Setup(x => x.GetBuild(It.IsAny <int>(), It.IsAny <int>())).Returns(buildResponse);

            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "consoleText")))
            .Returns(modifiedArtifact1);

            await buildBuildService.CheckBuildAsync(product3.Id);

            var builds = ReadTestData <AppDbContext, ProductBuild>();

            Assert.Single(builds);
            var modifiedProductBuild = builds.FirstOrDefault();

            Assert.False(modifiedProductBuild.Success);

            // Verify that notifications are sent to the user and the org admin
            var notifications = ReadTestData <AppDbContext, Notification>();

            Assert.Equal(2, notifications.Count);
            Assert.Equal($"{{\"projectName\":\"Test Project2\",\"productName\":\"TestProd1\",\"buildStatus\":\"completed\",\"buildError\":\"Error\",\"buildEngineUrl\":\"https://buildengine.testorg2/build-admin/view?id=2\",\"consoleText\":\"https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7-output.log\",\"projectId\":{product3.ProjectId},\"jobId\":2,\"buildId\":2,\"projectUrl\":\"https://dev.scriptoria.io/projects/2\"}}", notifications[0].MessageSubstitutionsJson);
            Assert.Equal("buildFailedAdmin", notifications[0].MessageId);
            Assert.Equal("https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7-output.log", notifications[0].LinkUrl);
        }
        public async Task Build_Check_BuildAsync()
        {
            BuildTestData();
            var buildBuildService     = _fixture.GetService <BuildEngineBuildService>();
            var mockBuildEngine       = Mock.Get(buildBuildService.BuildEngineApi);
            var mockWebRequestWrapper = Mock.Get(buildBuildService.WebRequestWrapper);
            var mockWebClient         = Mock.Get(buildBuildService.WebClient);

            mockBuildEngine.Reset();
            mockWebRequestWrapper.Reset();
            mockWebClient.Reset();
            var modifiedArtifact1 = new ProductArtifact
            {
                ProductId    = product1.Id,
                ArtifactType = "apk",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7.apk",
                ContentType  = "application/octet-stream",
                FileSize     = 8684905,
                LastModified = DateTime.UtcNow
            };
            var modifiedArtifact2 = new ProductArtifact
            {
                ProductId    = product1.Id,
                ArtifactType = "about",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/about.txt",
                ContentType  = "text/plain",
                FileSize     = 1831,
                LastModified = DateTime.UtcNow
            };
            var modifiedArtifact3 = new ProductArtifact
            {
                ProductId    = product1.Id,
                ArtifactType = "version",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/version.json",
                ContentType  = "application/json",
                FileSize     = 1831,
                LastModified = DateTime.UtcNow
            };
            var modifiedArtifact4 = new ProductArtifact
            {
                ProductId    = product1.Id,
                ArtifactType = "play-listing-manifest",
                Url          = "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/play-listing/manifest.json",
                ContentType  = "application/json",
                FileSize     = 1831,
                LastModified = DateTime.UtcNow
            };
            var storetype = AddEntity <AppDbContext, StoreType>(new StoreType
            {
                Id          = 1,
                Name        = "google_play_store",
                Description = "Google Play Store"
            });
            var language = AddEntity <AppDbContext, StoreLanguage>(new StoreLanguage
            {
                Id          = 1,
                Name        = "en-US",
                Description = "English (United States) – en-US",
                StoreTypeId = 1
            });

            var artifacts = new Dictionary <string, string>()
            {
                { "apk", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/English_Greek-4.7.apk" },
                { "about", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/about.txt" },
                { "version", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/version.json" },
                { "play-listing-manifest", "https://sil-stg-aps-artifacts.s3.amazonaws.com/stg/jobs/build_scriptureappbuilder_1/2/play-listing/manifest.json" }
            };

            product2.WorkflowBuildId = 42;

            var productBuild = AddEntity <AppDbContext, ProductBuild>(new ProductBuild
            {
                ProductId = product2.Id,
                BuildId   = 42,
            });

            var buildResponse = new BuildResponse
            {
                Id        = 2,
                JobId     = 1,
                Status    = "completed",
                Result    = "SUCCESS",
                Error     = "",
                Artifacts = artifacts
            };

            mockBuildEngine.Setup(x => x.GetBuild(It.Is <int>(i => i == product2.WorkflowJobId),
                                                  It.Is <int>(b => b == product2.WorkflowBuildId)))
            .Returns(buildResponse);
            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "apk")))
            .Returns(modifiedArtifact1);
            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "about")))
            .Returns(modifiedArtifact2);
            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "version")))
            .Returns(modifiedArtifact3);
            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "play-listing-manifest")))
            .Returns(modifiedArtifact4);

            mockWebClient.Setup(x => x.DownloadString(It.Is <string>(addr => addr == modifiedArtifact3.Url)))
            .Returns("{ \"version\" : \"4.7.6\", \"versionName\" : \"4.7\", \"versionCode\" : \"6\" } ");
            mockWebClient.Setup(x => x.DownloadString(It.Is <string>(addr => addr == modifiedArtifact4.Url)))
            .Returns("{ \"default-language\" : \"en-US\" }");
            await buildBuildService.CheckBuildAsync(product2.Id);

            mockBuildEngine.Verify(x => x.SetEndpoint(
                                       It.Is <String>(u => u == org1.BuildEngineUrl),
                                       It.Is <String>(t => t == org1.BuildEngineApiAccessToken)
                                       ));
            var modifiedArtifacts = ReadTestData <AppDbContext, ProductArtifact>();

            Assert.Equal(4, modifiedArtifacts.Count);
            var modifiedApk = modifiedArtifacts.First(a => a.ArtifactType == modifiedArtifact1.ArtifactType);

            Assert.Equal(modifiedArtifact1.Url, modifiedApk.Url);
            Assert.Equal(modifiedArtifact1.ContentType, modifiedApk.ContentType);
            Assert.Equal(modifiedArtifact1.FileSize, modifiedApk.FileSize);
            var modifiedProductBuilds = ReadTestData <AppDbContext, ProductBuild>();

            Assert.Single(modifiedProductBuilds);
            var build = modifiedProductBuilds.First();

            Assert.Equal("4.7.6", build.Version);
            Assert.True(build.Success);
            var modifiedProduct = ReadTestData <AppDbContext, Product>().Where(p => p.Id == product2.Id);

            Assert.Single(modifiedProduct);
            var product = modifiedProduct.First();

            Assert.Equal("en-US", product.StoreLanguage.Name);
            Assert.Equal("4.7.6", product.VersionBuilt);
            // One notification should be sent to owner on successful build
            var notifications = ReadTestData <AppDbContext, Notification>();

            Assert.Single(notifications);
            Assert.Equal("{\"projectName\":\"Test Project1\",\"productName\":\"TestProd1\"}", notifications[0].MessageSubstitutionsJson);
            Assert.Equal("buildCompletedSuccessfully", notifications[0].MessageId);
        }
        public async Task Get_Build_Check_Failure_Missing_ConsoleLog_Default_Build_Engine()
        {
            BuildTestData();
            var buildBuildService     = _fixture.GetService <BuildEngineBuildService>();
            var mockBuildEngine       = Mock.Get(buildBuildService.BuildEngineApi);
            var mockWebRequestWrapper = Mock.Get(buildBuildService.WebRequestWrapper);
            var mockWebClient         = Mock.Get(buildBuildService.WebClient);

            mockBuildEngine.Reset();
            mockWebRequestWrapper.Reset();
            mockWebClient.Reset();

            var productBuild = AddEntity <AppDbContext, ProductBuild>(new ProductBuild
            {
                ProductId = product3.Id,
                BuildId   = product3.WorkflowBuildId
            });
            var buildResponse = new BuildResponse
            {
                Id        = 2,
                JobId     = 1,
                Status    = "completed",
                Result    = "FAILURE",
                Error     = "Error",
                Artifacts = new Dictionary <string, string>()
                {
                    { "cloudWatch", "https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logEvent:group=/aws/codebuild/build_app-cth;stream=25de05b1-3a4c-4b5f-a423-e706348d9622" }
                }
            };
            var modifiedArtifact1 = new ProductArtifact
            {
                ProductId    = product3.Id,
                ArtifactType = "cloudWatch",
                Url          = "https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#logEvent:group=/aws/codebuild/build_app-cth;stream=25de05b1-3a4c-4b5f-a423-e706348d9622",
                LastModified = DateTime.UtcNow
            };


            mockBuildEngine.Setup(x => x.GetBuild(It.IsAny <int>(), It.IsAny <int>())).Returns(buildResponse);

            mockWebRequestWrapper.Setup(x => x.GetFileInfo(It.Is <ProductArtifact>(a =>
                                                                                   a.ArtifactType == "cloudWatch")))
            .Returns(modifiedArtifact1);

            await buildBuildService.CheckBuildAsync(product3.Id);

            var builds = ReadTestData <AppDbContext, ProductBuild>();

            Assert.Single(builds);
            var modifiedProductBuild = builds.FirstOrDefault();

            Assert.False(modifiedProductBuild.Success);

            // Verify that notifications are sent to the user and the org admin
            var notifications = ReadTestData <AppDbContext, Notification>();

            Assert.Equal(2, notifications.Count);
            Assert.Equal($"{{\"projectName\":\"Test Project2\",\"productName\":\"TestProd1\",\"buildStatus\":\"completed\",\"buildError\":\"Error\",\"buildEngineUrl\":\"https://buildengine.testorg2/build-admin/view?id=2\",\"consoleText\":null,\"projectId\":{product3.ProjectId},\"jobId\":2,\"buildId\":2,\"projectUrl\":\"https://dev.scriptoria.io/projects/2\"}}", notifications[0].MessageSubstitutionsJson);
            Assert.Equal("buildFailedAdmin", notifications[0].MessageId);
            Assert.Null(notifications[0].LinkUrl);
        }