Skip to content

Commit 34c8cf8

Browse files
author
MHD\nialan
committed
Started adding mocks for testing data lake services, refactored controllers to be services
1 parent 311db6b commit 34c8cf8

16 files changed

+334
-71
lines changed

Azure.DataPipelineTools.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
1212
LICENSE = LICENSE
1313
EndProjectSection
1414
EndProject
15+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataPipelineTools.Tests", "DataPipelineTools.Tests\DataPipelineTools.Tests.csproj", "{A2C01394-16F9-4783-9629-2857C400D6E6}"
16+
EndProject
1517
Global
1618
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1719
Debug|Any CPU = Debug|Any CPU
@@ -26,6 +28,10 @@ Global
2628
{84B7E7A9-BDE5-4602-B99C-853CC1159CE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
2729
{84B7E7A9-BDE5-4602-B99C-853CC1159CE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
2830
{84B7E7A9-BDE5-4602-B99C-853CC1159CE5}.Release|Any CPU.Build.0 = Release|Any CPU
31+
{A2C01394-16F9-4783-9629-2857C400D6E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32+
{A2C01394-16F9-4783-9629-2857C400D6E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
33+
{A2C01394-16F9-4783-9629-2857C400D6E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
34+
{A2C01394-16F9-4783-9629-2857C400D6E6}.Release|Any CPU.Build.0 = Release|Any CPU
2935
EndGlobalSection
3036
GlobalSection(SolutionProperties) = preSolution
3137
HideSolutionNode = FALSE

DataPipelineTools.Functions/Common/FunctionsBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public FunctionsBase(ILogger<FunctionsBase> logger)
1414
_logger = logger;
1515
}
1616

17-
protected JObject GetBaseResponse(DataLakeConfig dataLakeConfig, object parameters)
17+
protected JObject GetTemplateResponse(DataLakeConfig dataLakeConfig, object parameters)
1818
{
1919
var assemblyInfo = AssemblyHelpers.GetAssemblyVersionInfoJson();
2020

DataPipelineTools.Functions/Common/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public override void Configure(IFunctionsHostBuilder builder)
1616

1717
builder.Services.AddTransient(typeof(DataLakeConfigFactory));
1818
builder.Services.AddTransient<IDataLakeClientFactory, DataLakeClientFactory>();
19-
builder.Services.AddTransient(typeof(DataLakeControllerFactory));
19+
builder.Services.AddTransient(typeof(DataLakeServiceFactory));
2020
}
2121
}
2222
}

DataPipelineTools.Functions/DataLake/DataLakeConfigFactory.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ public DataLakeGetItemsConfig GetItemsConfig (HttpRequest req)
7777
return config;
7878
}
7979

80-
private IEnumerable<Filter<DataLakeFile>> ParseFilters(HttpRequest req)
80+
private IEnumerable<Filter<DataLakeItem>> ParseFilters(HttpRequest req)
8181
{
8282
var filters = req.Query.Keys
8383
.Where(k => k.StartsWith("filter[") && k.EndsWith("]"))
84-
.SelectMany(k => req.Query[k].Select(v => Filter<DataLakeFile>.ParseFilter(k, v, _logger)))
84+
.SelectMany(k => req.Query[k].Select(v => Filter<DataLakeItem>.ParseFilter(k, v, _logger)))
8585
.Where(f => f != null);
8686

8787
return filters;

DataPipelineTools.Functions/DataLake/DataLakeFunctions.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ public partial class DataLakeFunctions: FunctionsBase
1515
{
1616
private readonly DataLakeConfigFactory _configFactory;
1717
private readonly IDataLakeClientFactory _clientFactory;
18-
private readonly DataLakeControllerFactory _controllerFactory;
19-
public DataLakeFunctions(ILogger<DataLakeFunctions> logger, DataLakeConfigFactory configFactory, IDataLakeClientFactory clientFactory, DataLakeControllerFactory controllerFactory):
18+
private readonly DataLakeServiceFactory _serviceFactory;
19+
public DataLakeFunctions(ILogger<DataLakeFunctions> logger, DataLakeConfigFactory configFactory, IDataLakeClientFactory clientFactory, DataLakeServiceFactory serviceFactory):
2020
base(logger)
2121
{
2222
_configFactory = configFactory;
2323
_clientFactory = clientFactory;
24-
_controllerFactory = controllerFactory;
24+
_serviceFactory = serviceFactory;
2525
}
2626

2727

@@ -45,9 +45,9 @@ public async Task<IActionResult> DataLakeGetItems(
4545
throw new ArgumentException($"Account Uri '{dataLakeConfig.AccountUri}' not found. Check the URI is correct.");
4646

4747
var client = _clientFactory.GetDataLakeClient(dataLakeConfig);
48-
var controller = _controllerFactory.CreateDataLakeController(client);
48+
var controller = _serviceFactory.CreateDataLakeService(client);
4949

50-
var responseJson = GetBaseResponse(dataLakeConfig, getItemsConfig);
50+
var responseJson = GetTemplateResponse(dataLakeConfig, getItemsConfig);
5151
var items = await controller.GetItemsAsync(dataLakeConfig, getItemsConfig);
5252
foreach (var item in items)
5353
responseJson.Add(item.Key, item.Value);
@@ -84,15 +84,15 @@ public async Task<IActionResult> DataLakeCheckPathCase(
8484
throw new ArgumentException($"Account Uri '{dataLakeConfig.AccountUri}' not found. Check the URI is correct.");
8585

8686
var client = _clientFactory.GetDataLakeClient(dataLakeConfig);
87-
var controller = _controllerFactory.CreateDataLakeController(client);
87+
var controller = _serviceFactory.CreateDataLakeService(client);
8888

8989
var validatedPath = await controller.CheckPathAsync(getItemsConfig.Path, true);
9090

9191
// If multiple files match, the function will throw and the catch block will return a BadRequestObjectResult
9292
// If the path could not be found as a directory, try for a file...
9393
validatedPath ??= await controller.CheckPathAsync(getItemsConfig.Path, false);
9494

95-
var responseJson = GetBaseResponse(dataLakeConfig, getItemsConfig);
95+
var responseJson = GetTemplateResponse(dataLakeConfig, getItemsConfig);
9696
responseJson.Add("validatedPath", validatedPath);
9797

9898
return validatedPath != null ?
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using Azure;
9+
using Azure.Core.Extensions;
10+
using Azure.Storage.Files.DataLake;
11+
using Azure.Storage.Files.DataLake.Models;
12+
using Microsoft.Extensions.Logging;
13+
using Moq;
14+
using NUnit.Framework;
15+
using SqlCollaborative.Azure.DataPipelineTools.DataLake;
16+
using SqlCollaborative.Azure.DataPipelineTools.DataLake.Model;
17+
18+
namespace DataPipelineTools.Tests.DataLake
19+
{
20+
[TestFixture]
21+
public class DataLakeServiceTests: TestBase
22+
{
23+
private const string AccountUri = "mydatalake";
24+
private const string ContainerName = "mycontainer";
25+
26+
private readonly IEnumerable<PathItem> TestData;
27+
private readonly Mock<DataLakeFileSystemClient> mockFileSystemClient;
28+
29+
//private readonly Mock<DataLakeDirectoryClient> mockDirectoryClient;
30+
//private readonly Mock<DataLakeFileClient> mockFileClient;
31+
32+
private readonly Mock<ILogger<DataLakeServiceFactory>> mockLogger;
33+
34+
private readonly DataLakeService Sut;
35+
36+
public DataLakeServiceTests()
37+
{
38+
// Get test data to mock the file system
39+
TestData = GetTestData();
40+
41+
// Mock the logger to test where it is called
42+
mockLogger = new Mock<ILogger<DataLakeServiceFactory>>();
43+
44+
// Mock the file system client
45+
mockFileSystemClient = BuildMockDataLakeFileSystemClient();
46+
47+
// Use the factory to inject the mock logger to get the mock client...
48+
var factory = new DataLakeServiceFactory(mockLogger.Object);
49+
Sut = factory.CreateDataLakeService(mockFileSystemClient.Object);
50+
}
51+
52+
private Mock<DataLakeFileSystemClient> BuildMockDataLakeFileSystemClient()
53+
{
54+
var mockFileSystemClient = new Mock<DataLakeFileSystemClient>();
55+
mockFileSystemClient.SetupGet(x => x.Name).Returns(ContainerName);
56+
mockFileSystemClient.SetupGet(x => x.AccountName).Returns(AccountUri);
57+
58+
mockFileSystemClient
59+
.Setup(x => x.GetDirectoryClient(It.IsAny<String>()))
60+
.Returns<string>(BuildMockDataLakeDirectoryClient<DataLakeDirectoryClient>);
61+
mockFileSystemClient
62+
.Setup(x => x.GetFileClient(It.IsAny<String>()))
63+
.Returns<string>(BuildMockDataLakeDirectoryClient<DataLakeFileClient>);
64+
65+
mockFileSystemClient
66+
.Setup(x => x.GetPaths(It.IsAny<string>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<CancellationToken>()))
67+
.Returns((string p, bool r) =>
68+
{
69+
var items = TestData
70+
// Include all files starting with the test path
71+
.Where(x => x.Name.StartsWith(p))
72+
// Still include them if the recursive flag is set, otherwise check if the relative path after the search path contains
73+
// directory separator to exclude sub dirs
74+
.Where(x => r || !x.Name.Substring(p.Length).Contains('/'))
75+
.ToList()
76+
.AsReadOnly();
77+
78+
var page = Page<PathItem>.FromValues(items, null, Mock.Of<Response>());
79+
return Pageable<PathItem>.FromPages(new[] { page });
80+
});
81+
82+
return mockFileSystemClient;
83+
}
84+
85+
private T BuildMockDataLakeDirectoryClient<T>(string directoryName) where T: DataLakePathClient
86+
{
87+
var mockDirectoryClient = new Mock<T>();
88+
mockDirectoryClient.SetupGet(x => x.FileSystemName).Returns(ContainerName);
89+
mockDirectoryClient.SetupGet(x => x.AccountName).Returns(AccountUri);
90+
mockDirectoryClient.SetupGet(x => x.Name).Returns(directoryName);
91+
92+
var directoryNameExists = TestData.Any(i => i.Name == directoryName);
93+
mockDirectoryClient
94+
.Setup(x => x.ExistsAsync(It.IsAny<CancellationToken>()))
95+
.ReturnsAsync(() => Response.FromValue(directoryNameExists, new Mock<Response>().Object));
96+
97+
mockDirectoryClient
98+
.Setup(x => x.Exists(It.IsAny<CancellationToken>()))
99+
.Returns(() => Response.FromValue(directoryNameExists, new Mock<Response>().Object));
100+
101+
return mockDirectoryClient.Object;
102+
}
103+
104+
private IEnumerable<PathItem> GetTestData()
105+
{
106+
return GetTestData(",", properties =>
107+
{
108+
return DataLakeModelFactory.PathItem(
109+
properties[nameof(PathItem.Name)],
110+
Convert.ToBoolean(properties[nameof(PathItem.IsDirectory)]),
111+
Convert.ToDateTime(properties[nameof(PathItem.LastModified)]),
112+
ETag.All,
113+
Convert.ToInt32(properties[nameof(PathItem.ContentLength)]),
114+
null,
115+
null,
116+
null
117+
);
118+
}).ToArray();
119+
}
120+
121+
//private IEnumerable<DataLakeItem> GetTestData()
122+
// {
123+
// return GetTestData(",", properties =>
124+
// {
125+
// return new DataLakeItem()
126+
// {
127+
// Directory = properties[nameof(DataLakeItem.Directory)],
128+
// Name = properties[nameof(DataLakeItem.Name)],
129+
// IsDirectory = Convert.ToBoolean(properties[nameof(DataLakeItem.IsDirectory)]),
130+
// ContentLength = Convert.ToInt32(properties[nameof(DataLakeItem.ContentLength)]),
131+
// LastModified = Convert.ToDateTime(properties[nameof(DataLakeItem.LastModified)])
132+
// };
133+
// }).ToArray();
134+
// }
135+
136+
[SetUp]
137+
public void Setup()
138+
{
139+
140+
}
141+
142+
[Test]
143+
public void CheckPathAsync_ShouldReturn_()
144+
{
145+
146+
}
147+
148+
149+
}
150+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Name,IsDirectory,ContentLength,LastModified
2+
raw,TRUE,0,01/01/2021 12:00
3+
raw/database,TRUE,0,01/01/2021 13:00
4+
raw/database/jan/extract_1.csv,FALSE,10,02/01/2021 13:00
5+
raw/database/feb/extract_2.csv,FALSE,20,03/01/2021 13:00
6+
raw/api,TRUE,0,01/01/2021 14:00
7+
raw/api/jan/delta_extract_1.json,FALSE,10,01/01/2021 14:00
8+
raw/api/jan/delta_extract_2.json,FALSE,20,02/01/2021 14:00
9+
raw/api/feb/delta_extract_3.json,FALSE,30,03/01/2021 14:00
10+
raw/API,TRUE,0,01/01/2021 15:00
11+
raw/API/jan/delta_extract_1.json,FALSE,10,01/01/2021 15:00
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using NUnit.Framework;
2+
using SqlCollaborative.Azure.DataPipelineTools.DataLake.Model;
3+
4+
namespace DataPipelineTools.Tests.DataLake.Model
5+
{
6+
[TestFixture]
7+
public class DataLakeConfigTests
8+
{
9+
private const string AccountUri = "mydatalake";
10+
private const string ContainerName = "mycontainer";
11+
[SetUp]
12+
public void Setup()
13+
{
14+
}
15+
16+
[Test]
17+
public void BaseUrl_ShouldContain_AccountUri()
18+
{
19+
var config = new DataLakeConfig { AccountUri = AccountUri, Container = ContainerName };
20+
21+
Assert.IsTrue(config.BaseUrl.StartsWith($"https://{AccountUri}"));
22+
}
23+
24+
public void BaseUrl_ShouldContain_ContainerName()
25+
{
26+
var config = new DataLakeConfig { AccountUri = AccountUri, Container = ContainerName };
27+
28+
Assert.IsTrue(config.BaseUrl.EndsWith(ContainerName));
29+
}
30+
}
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Moq" Version="4.16.1" />
11+
<PackageReference Include="NUnit" Version="3.12.0" />
12+
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
13+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
14+
</ItemGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\DataPipelineTools\DataPipelineTools.csproj" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<None Update="DataLake\DataLakeServiceTests_Data_DataLakeItem - Copy.csv">
22+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
23+
</None>
24+
<None Update="DataLake\DataLakeServiceTests_Data_DataLakeItem.csv">
25+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
26+
</None>
27+
</ItemGroup>
28+
29+
</Project>

DataPipelineTools.Tests/TestBase.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Reflection;
5+
using System.Text;
6+
using DataPipelineTools.Tests.DataLake;
7+
using SqlCollaborative.Azure.DataPipelineTools.DataLake.Model;
8+
9+
namespace DataPipelineTools.Tests
10+
{
11+
public abstract class TestBase
12+
{
13+
//protected abstract T ParseCsv(string csvLine);
14+
15+
protected IEnumerable<T> GetTestData<T>(string delimiter, Func<Dictionary<string, string>, T> conversionFunc)
16+
{
17+
var thisAssembly = Assembly.GetExecutingAssembly();
18+
var assemblyPath = thisAssembly.Location;
19+
var assemblyName = thisAssembly.GetName().Name;
20+
var nameSpace = GetType().Namespace;
21+
22+
var testDataRelativePath = nameSpace.Replace(assemblyName, "").Replace(".", "\\").TrimStart('\\');
23+
var testDataPath = Path.Combine(Path.GetDirectoryName(assemblyPath), testDataRelativePath, $"{GetType().Name}_Data_{typeof(T).Name}.csv");
24+
25+
using (var fs = new FileStream(testDataPath, FileMode.Open, FileAccess.Read, FileShare.Read))
26+
{
27+
using (StreamReader sr = new StreamReader(fs))
28+
{
29+
// The first line is the headers, so grab the values as keys
30+
var keys = sr.ReadLine().Split(delimiter);
31+
32+
// Build a dictionary of the properties, as pass it to the parser function
33+
while (!sr.EndOfStream)
34+
{
35+
var dict = new Dictionary<string, string>();
36+
var values = sr.ReadLine().Split(delimiter);
37+
for (int i = 0; i < keys.Length && i < values.Length; i++)
38+
{
39+
dict.Add(keys[i], values[i]);
40+
}
41+
42+
yield return conversionFunc(dict);
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)