Skip to content

Commit

Permalink
VIH-9699 run tests inside a container (#557)
Browse files Browse the repository at this point in the history
  • Loading branch information
shaed-parkar authored May 11, 2023
1 parent fb8b051 commit e1d83df
Show file tree
Hide file tree
Showing 135 changed files with 3,016 additions and 3,610 deletions.
12 changes: 12 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-reportgenerator-globaltool": {
"version": "5.1.17",
"commands": [
"reportgenerator"
]
}
}
}
10 changes: 7 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ ClientBin/
*.publishsettings
orleans.codegen.cs

# Including strong name files can present a security risk
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk

Expand Down Expand Up @@ -316,7 +316,7 @@ __pycache__/
# OpenCover UI analysis results
OpenCover/

# Azure Stream Analytics local run output
# Azure Stream Analytics local run output
ASALocalRun/

# MSBuild Binary and Structured Log
Expand All @@ -325,7 +325,7 @@ ASALocalRun/
# NVidia Nsight GPU debugger configuration file
*.nvuser

# MFractors (Xamarin productivity tool) working folder
# MFractors (Xamarin productivity tool) working folder
.mfractor/

# SonarQube
Expand All @@ -342,4 +342,8 @@ ASALocalRun/
values.dev.yaml
secrets.dev.yaml

.env
.DS_Store
Coverage/
**/SpecFlow/
azurite/
46 changes: 23 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# vh-video-api

## HMCTS

[![Build Status](https://hmctsreform.visualstudio.com/VirtualHearings/_apis/build/status/Apps-CI/hmcts.vh-video-api?repoName=hmcts%2Fvh-video-api&branchName=master)](https://hmctsreform.visualstudio.com/VirtualHearings/_build/latest?definitionId=107&repoName=hmcts%2Fvh-video-api&branchName=master)

[![VideoApi.Client package in vh-packages feed in Azure Artifacts](https://hmctsreform.feeds.visualstudio.com/3f69a23d-fbc7-4541-afc7-4cccefcad773/_apis/public/Packaging/Feeds/vh-packages/Packages/80002570-7840-44ca-8d91-58fe07774f40/Badge)](https://hmctsreform.visualstudio.com/VirtualHearings/_artifacts/feed/vh-packages/NuGet/VideoApi.Client?preferRelease=true)

[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vh-video-api&metric=alert_status)](https://sonarcloud.io/dashboard?id=vh-video-api)

## SDS

[![Build Status](https://dev.azure.com/hmcts/Video%20Hearings/_apis/build/status/vh-video-api/hmcts.vh-video-api.sds.master-release?repoName=hmcts%2Fvh-video-api&branchName=master)](https://dev.azure.com/hmcts/Video%20Hearings/_build/latest?definitionId=668&repoName=hmcts%2Fvh-video-api&branchName=master)

[![VideoApi.Client package in vh-packages feed in Azure Artifacts](https://feeds.dev.azure.com/hmcts/cf3711aa-2aed-4f62-81a8-2afaee0ce26d/_apis/public/Packaging/Feeds/vh-packages/Packages/2cd477d4-635e-48e1-987f-1d91d35179a6/Badge)](https://dev.azure.com/hmcts/Video%20Hearings/_artifacts/feed/vh-packages/NuGet/VideoApi.Client?preferRelease=true)


[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=vh-video-api&metric=alert_status)](https://sonarcloud.io/dashboard?id=vh-video-api)

## Running Sonar Analysis

``` bash
Expand All @@ -24,7 +41,7 @@ dotnet test --no-build VideoApi.IntegrationTests/VideoApi.IntegrationTests.cspro
Under the unit test project directory

``` bash
dotnet reportgenerator "-reports:../Artifacts/Coverage/coverage.opencover.xml" "-targetDir:../Artifacts/Coverage/Report" -reporttypes:HtmlInline_AzurePipelines
dotnet reportgenerator "-reports:./Coverage/coverage.opencover.xml" "-targetDir:./Artifacts/Coverage/Report" -reporttypes:Html -sourcedirs:./VideoApi
```

##Branch name git hook will run on pre commit and control the standard for new branch name.
Expand Down Expand Up @@ -54,30 +71,13 @@ Update following configuration under appsettings.json under VideoApi.AcceptanceT

Note: Ensure you have Docker desktop engine installed and setup

## Run Stryker

To run stryker mutation test, go to UnitTest folder under command prompt and run the following command

```bash
dotnet stryker
```

From the results look for line(s) of code highlighted with Survived\No Coverage and fix them.
### Running all tests in Docker

Open a terminal at the root level of the repository and run the following command

If in case you have not installed stryker previously, please use one of the following commands

### Global
```bash
dotnet tool install -g dotnet-stryker
```
### Local
```bash
dotnet tool install dotnet-stryker
```console
docker-compose -f "docker-compose.tests.yml" up --build --abort-on-container-exit
```

To update latest version of stryker please use the following command

```bash
dotnet tool update --global dotnet-stryker
```
> You may need to create a `.env` file to store the environment variables
132 changes: 132 additions & 0 deletions VideoApi/Testing.Common/AzureStorageManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure;
using Azure.Storage;
using Azure.Storage.Blobs;
using NUnit.Framework;

namespace Testing.Common
{
public class AzureStorageManager
{
#pragma warning disable
// This the default test secret available in public MS documentation
private static readonly string DefaultAzuriteConnectionString =
"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;";
#pragma warning restore
private string _storageContainerName;

private readonly string _storageAccountName;
private readonly string _storageAccountKey;

private BlobContainerClient _blobContainerClient;
private BlobClient _blobClient;

public AzureStorageManager()
{
}

public AzureStorageManager(string accountName, string accountKey, string containerName)
{
_storageAccountName = accountName;
_storageAccountKey = accountKey;
_storageContainerName = containerName;
}

public AzureStorageManager SetStorageContainerName(string storageContainerName)
{
_storageContainerName = storageContainerName;
return this;
}

public AzureStorageManager CreateBlobClient(string filePathWithoutExtension)
{
_blobContainerClient = CreateContainerClient();
_blobClient = _blobContainerClient.GetBlobClient($"{filePathWithoutExtension}.mp4");
return this;
}

public AzureStorageManager CreateBlobClient(string filePathWithoutExtension, string connectionString)
{
_blobContainerClient = CreateContainerClient(connectionString);
_blobClient = _blobContainerClient.GetBlobClient($"{filePathWithoutExtension}.mp4");
return this;
}

public AzureStorageManager CreateBlobContainerClient(string connectionString)
{
_blobContainerClient = CreateContainerClient(connectionString);
return this;
}

public async IAsyncEnumerable<BlobClient> GetAllBlobsAsync(string filePathNamePrefix)
{
await foreach (var page in _blobContainerClient.GetBlobsAsync(prefix: filePathNamePrefix))
{
yield return _blobContainerClient.GetBlobClient(page.Name);
}
}

public async Task UploadAudioFileToStorage(string file)
{
await _blobClient.UploadAsync(file);

if (!await _blobClient.ExistsAsync())
{
throw new RequestFailedException($"Can not find file: {file}");
}

TestContext.WriteLine($"Uploaded audio file to : {file}");
}

public async Task UploadFileToStorage(string localFileName, string filePathOnStorage)
{
var blobClient = _blobContainerClient.GetBlobClient(filePathOnStorage);

await blobClient.UploadAsync(localFileName);

if (!await blobClient.ExistsAsync())
{
throw new RequestFailedException($"Can not find file: {localFileName} with full path {filePathOnStorage}");
}

TestContext.WriteLine($"Uploaded audio file to : {localFileName} with full path: {filePathOnStorage}");
}

public async Task<bool> VerifyAudioFileExistsInStorage()
{
return await _blobClient.ExistsAsync();
}

public async Task RemoveAudioFileFromStorage()
{
await _blobClient.DeleteAsync();
TestContext.WriteLine("Deleted audio file");
}

public static BlobServiceClient CreateAzuriteBlobServiceClient(string connectionString)
{
connectionString ??= DefaultAzuriteConnectionString;
TestContext.WriteLine($"Azure Connection string {connectionString}");
var serviceClient = new BlobServiceClient(connectionString, new BlobClientOptions {Retry = { MaxRetries = 2}});
return serviceClient;
}

private BlobContainerClient CreateContainerClient(string connectionString)
{
var serviceClient = CreateAzuriteBlobServiceClient(connectionString);
var containerClient = serviceClient.GetBlobContainerClient(_storageContainerName);
containerClient.CreateIfNotExists();
return containerClient;
}

private BlobContainerClient CreateContainerClient()
{
var storageSharedKeyCredential = new StorageSharedKeyCredential(_storageAccountName, _storageAccountKey);
var serviceEndpoint = $"https://{_storageAccountName}.blob.core.windows.net/";
var serviceClient = new BlobServiceClient(new Uri(serviceEndpoint), storageSharedKeyCredential);
return serviceClient.GetBlobContainerClient(_storageContainerName);
}
}
}
24 changes: 24 additions & 0 deletions VideoApi/Testing.Common/Configuration/ConfigRootBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Microsoft.Extensions.Configuration;

namespace Testing.Common.Configuration
{
public static class ConfigRootBuilder
{
private const string UserSecretId = "9AECE566-336D-4D16-88FA-7A76C27321CD";
public static IConfigurationRoot Build(string userSecretId = UserSecretId, bool useSecrets = true)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile("appsettings.Development.json", true)
.AddJsonFile("appsettings.Production.json", true); // CI write variables in the pipeline to this file

if (useSecrets)
{
builder = builder.AddUserSecrets(userSecretId);
}

return builder.AddEnvironmentVariables()
.Build();
}
}
}
37 changes: 37 additions & 0 deletions VideoApi/Testing.Common/FileManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.IO;
using System.Reflection;

namespace Testing.Common
{
public static class FileManager
{
public static string GetAssemblyDirectory()
{
return AppDomain.CurrentDomain.BaseDirectory;
}

public static string CreateNewAudioFile(string originalFileName, string fileNameWithoutExtension, string path = "TestAudioFiles")
{
var originalFilePath = Path.Join(GetAssemblyDirectory(), path, originalFileName);
if (!File.Exists(originalFilePath))
{
throw new FileNotFoundException($"Unable to find audio file with path : {originalFilePath}");
}

var fileWithExtension = $"{fileNameWithoutExtension}.mp4";
var newFilePath = Path.Join(GetAssemblyDirectory(), path, fileWithExtension);
File.Copy(originalFilePath, newFilePath, true);
return newFilePath;
}

public static void RemoveLocalAudioFile(string filepath)
{
if (!File.Exists(filepath))
{
throw new FileNotFoundException($"Unable to find audio file with path : {filepath}");
}
File.Delete(filepath);
}
}
}
17 changes: 13 additions & 4 deletions VideoApi/Testing.Common/Helper/ApiUriFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,20 @@ public static class EventsEndpoints
public static class AudioRecordingEndpoints
{
private const string ApiRoot = "conferences";

[Obsolete("We only have one application for all hearings now. Need to review old bookings.")]
public static string GetAudioApplicationWithHearingId(Guid hearingId) => $"{ApiRoot}/audioapplications/{hearingId}";

[Obsolete("We only have one application for all hearings now. Need to review old bookings.")]
public static string GetAudioApplication() => $"{ApiRoot}/audioapplications";

[Obsolete("We only have one application for all hearings now. Need to review old bookings.")]
public static string DeleteAudioApplication(Guid hearingId) => $"{ApiRoot}/audioapplications/{hearingId}";
public static string GetAudioStream(Guid hearingId) => $"{ApiRoot}/audiostreams/{hearingId}";

[Obsolete("We only have one application for all hearings now. Need to review old bookings.")]
public static string GetAudioMonitoringStream(Guid hearingId) => $"{ApiRoot}/audiostreams/{hearingId}/monitoring";

public static string GetAudioStream(Guid hearingId) => $"{ApiRoot}/audiostreams/{hearingId}";
public static string GetAudioRecordingLink(Guid hearingId) => $"{ApiRoot}/audio/{hearingId}";
public static string GetCvpAudioRecordingsAll(string cloudRoom, string date, string caseReference) => $"{ApiRoot}/audio/cvp/all/{cloudRoom}/{date}/{caseReference}";
public static string GetCvpAudioRecordingsByCloudRoom(string cloudRoom, string date) => $"{ApiRoot}/audio/cvp/cloudroom/{cloudRoom}/{date}";
Expand Down Expand Up @@ -97,7 +106,7 @@ public static class InstantMessageEndpoints
public static string RemoveInstantMessagesForConference(Guid conferenceId) => $"{ApiRoot}/{conferenceId}/instantmessages";
public static string GetClosedConferencesWithInstantMessages => $"{ApiRoot}/expiredIM";
}

public static class EPEndpoints
{
private const string ApiRoot = "conferences";
Expand All @@ -123,10 +132,10 @@ public static class VirtualRoomEndpoints

public static string GetInterpreterRoomForParticipant(Guid conferenceId, Guid participantId) =>
$"{ApiRoot}/{conferenceId}/rooms/interpreter/{participantId}";

public static string GetWitnessRoomForParticipant(Guid conferenceId, Guid participantId) =>
$"{ApiRoot}/{conferenceId}/rooms/witness/{participantId}";

public static string GetJudicialRoomForParticipant(Guid conferenceId, Guid participantId) =>
$"{ApiRoot}/{conferenceId}/rooms/judicial/{participantId}";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public ConferenceBuilder(bool ignoreId = false, Guid? knownHearingRefId = null,

var hearingRefId = knownHearingRefId ?? Guid.NewGuid();

var scheduleDateTime = scheduledDateTime ?? DateTime.UtcNow.AddMinutes(30);
var scheduleDateTime = scheduledDateTime ?? DateTime.Today.AddHours(9).AddMinutes(30);
const string caseType = "Generic";
var randomGenerator = RandomNumberGenerator.Create(); // Compliant for security-sensitive use cases
var data = new byte[2];
Expand Down
8 changes: 7 additions & 1 deletion VideoApi/Testing.Common/Testing.Common.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latestmajor</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
<PackageReference Include="Faker.NETCore" Version="1.0.2" />
<PackageReference Include="FluentAssertions" Version="5.10.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
Expand All @@ -12,6 +13,11 @@
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="NBuilder" Version="6.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.56.0.67649">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VideoApi.Common\VideoApi.Common.csproj" />
Expand Down
Loading

0 comments on commit e1d83df

Please sign in to comment.