diff --git a/FilterLists.sln b/FilterLists.sln
index c7a2d54a6..938e5458d 100644
--- a/FilterLists.sln
+++ b/FilterLists.sln
@@ -40,6 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{041F9CFA
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{B94A5BCD-3BE3-4FE4-AE48-ADCF904A22A4}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilterLists.Agent.Tests", "tests\FilterLists.Agent.Tests\FilterLists.Agent.Tests.csproj", "{CF6574D2-E056-496F-BB35-C79C324B0075}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -78,6 +80,10 @@ Global
{B94A5BCD-3BE3-4FE4-AE48-ADCF904A22A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B94A5BCD-3BE3-4FE4-AE48-ADCF904A22A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B94A5BCD-3BE3-4FE4-AE48-ADCF904A22A4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CF6574D2-E056-496F-BB35-C79C324B0075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CF6574D2-E056-496F-BB35-C79C324B0075}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CF6574D2-E056-496F-BB35-C79C324B0075}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CF6574D2-E056-496F-BB35-C79C324B0075}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -90,6 +96,7 @@ Global
{4F39D132-498D-4A93-9F6C-1C604718300D} = {CE4AB25B-5501-42E9-B8B4-87B9C20472AC}
{7D9F0D85-E906-401A-BDAD-724440B34567} = {041F9CFA-B6F7-4DEB-9441-829DE697B1BB}
{DA34DED7-B352-4855-9C7B-9E3D983DBE2E} = {041F9CFA-B6F7-4DEB-9441-829DE697B1BB}
+ {CF6574D2-E056-496F-BB35-C79C324B0075} = {041F9CFA-B6F7-4DEB-9441-829DE697B1BB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8A31B449-52DD-4445-A9C4-033A24E25308}
diff --git a/docker-compose.agent.tests.yml b/docker-compose.agent.tests.yml
new file mode 100644
index 000000000..198c3f5cf
--- /dev/null
+++ b/docker-compose.agent.tests.yml
@@ -0,0 +1,20 @@
+version: "3.7"
+
+services:
+ api:
+ image: collinbarrett/filterlists.agent:test-agent
+ build:
+ context: .
+ dockerfile: src/FilterLists.Agent/Dockerfile
+ target: test-agent
+ networks:
+ - test-agent
+ volumes:
+ - test-agent-results:/app/tests/FilterLists.Agent.Tests/TestResults
+
+networks:
+ test-agent:
+
+volumes:
+ test-agent-results:
+ external: true
diff --git a/src/FilterLists.Agent/Dockerfile b/src/FilterLists.Agent/Dockerfile
index e6b296871..926a2ec6a 100644
--- a/src/FilterLists.Agent/Dockerfile
+++ b/src/FilterLists.Agent/Dockerfile
@@ -18,6 +18,19 @@ RUN dotnet restore
COPY src/FilterLists.Agent/. .
RUN dotnet build -c Release --no-restore
+# init Agent.Tests
+FROM build AS test-agent
+ENTRYPOINT ["dotnet", "test", "--logger:trx"]
+
+# restore Agent.Tests
+WORKDIR /app/tests/FilterLists.Agent.Tests
+COPY tests/FilterLists.Agent.Tests/FilterLists.Agent.Tests.csproj .
+RUN dotnet restore
+
+# build Agent.Tests
+COPY tests/FilterLists.Agent.Tests/. .
+RUN dotnet build -c Release --no-restore
+
# publish
FROM build AS publish
RUN dotnet publish -c Release -o out --no-restore --no-build
diff --git a/tests/FilterLists.Agent.Tests/FilterLists.Agent.Tests.csproj b/tests/FilterLists.Agent.Tests/FilterLists.Agent.Tests.csproj
new file mode 100644
index 000000000..a2e89e56e
--- /dev/null
+++ b/tests/FilterLists.Agent.Tests/FilterLists.Agent.Tests.csproj
@@ -0,0 +1,49 @@
+
+
+
+ netcoreapp2.2
+ true
+ latest
+ Collin M. Barrett
+ Collin M. Barrett
+ FilterLists
+ The independent, comprehensive directory of filter and host lists for advertisements, trackers, malware, and annoyances.
+ Copyright (c) 2019 Collin M. Barrett
+ https://github.com/collinbarrett/FilterLists/blob/master/LICENSE
+ https://filterlists.com/
+ https://github.com/collinbarrett/FilterLists/raw/master/imgs/icon_filterlists.png
+ git
+ https://github.com/collinbarrett/FilterLists
+
+
+
+ FilterLists.Agent.Tests.ruleset
+
+
+
+ FilterLists.Agent.Tests.ruleset
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
diff --git a/tests/FilterLists.Agent.Tests/FilterLists.Agent.Tests.ruleset b/tests/FilterLists.Agent.Tests/FilterLists.Agent.Tests.ruleset
new file mode 100644
index 000000000..a3be703a8
--- /dev/null
+++ b/tests/FilterLists.Agent.Tests/FilterLists.Agent.Tests.ruleset
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/FilterLists.Agent.Tests/ReadOnlyListExtensionsTests.cs b/tests/FilterLists.Agent.Tests/ReadOnlyListExtensionsTests.cs
new file mode 100644
index 000000000..f24ccb894
--- /dev/null
+++ b/tests/FilterLists.Agent.Tests/ReadOnlyListExtensionsTests.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using FilterLists.Agent.Extensions;
+using Xunit;
+
+namespace FilterLists.Agent.Tests
+{
+ public class ReadOnlyListExtensionsTests
+ {
+ [Fact]
+ public void FirstOrDefault_WithEmptyList_ReturnsDefault()
+ {
+ var list = (IReadOnlyList)new List();
+ var sut = list.FirstOrDefault();
+ Assert.Equal(default, sut);
+ }
+
+ [Fact]
+ public void FirstOrDefault_WithMoreThanOneListElement_ReturnsFirstListElement()
+ {
+ var list = (IReadOnlyList)new List {1, 2, 3};
+ var sut = list.FirstOrDefault();
+ Assert.Equal(1, sut);
+ }
+
+ [Fact]
+ public void FirstOrDefault_WithNullParameter_ThrowsArgumentNullException()
+ {
+ var list = (IReadOnlyList)null;
+ Assert.Throws(() => list.FirstOrDefault());
+ }
+
+ [Fact]
+ public void FirstOrDefault_WithOneListElement_ReturnsFirstListElement()
+ {
+ var list = (IReadOnlyList)new List {0};
+ var sut = list.FirstOrDefault();
+ Assert.Equal(0, sut);
+ }
+ }
+}
\ No newline at end of file