diff --git a/Directory.Packages.props b/Directory.Packages.props index 04c02383..d3595999 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -13,6 +13,10 @@ + + + + @@ -23,6 +27,7 @@ + @@ -32,7 +37,9 @@ + + @@ -45,4 +52,4 @@ - + \ No newline at end of file diff --git a/HealthChecks.sln b/HealthChecks.sln index a73f2c01..1e0c7f5a 100644 --- a/HealthChecks.sln +++ b/HealthChecks.sln @@ -191,6 +191,42 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Dapr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Dapr.Tests.Integration", "src\HealthChecks.Dapr\tests\NetEvolve.HealthChecks.Dapr.Tests.Integration\NetEvolve.HealthChecks.Dapr.Tests.Integration.csproj", "{21D5C2DB-3EC0-4B6A-B9CF-C3FED69168B7}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks.Azure.Blobs", "HealthChecks.Azure.Blobs", "{7D78D5FB-924A-45E1-9C53-D9AFEA9451AC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F24F1664-82F5-4655-AD66-94A5331E11B7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Blobs", "src\HealthChecks.Azure.Blobs\src\NetEvolve.HealthChecks.Azure.Blobs\NetEvolve.HealthChecks.Azure.Blobs.csproj", "{15BB9AE6-EFC3-417C-90F9-0B223CF685B5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{615B6749-5056-476C-BF10-E84807E5A15F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit", "src\HealthChecks.Azure.Blobs\tests\NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit\NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit.csproj", "{AD913B62-E270-4503-B9E1-1F69BEFB9CA3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration", "src\HealthChecks.Azure.Blobs\tests\NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration\NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration.csproj", "{33C2C63B-F1D8-4710-82CA-015667224F77}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks.Azure.Queues", "HealthChecks.Azure.Queues", "{93C36BE6-1F4A-4672-841F-A3D56A5909B3}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{51D0D687-7C08-4A6C-9060-C069476F46E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Queues", "src\HealthChecks.Azure.Queues\src\NetEvolve.HealthChecks.Azure.Queues\NetEvolve.HealthChecks.Azure.Queues.csproj", "{06089C14-C49F-4C4D-B690-05AEBC8FD822}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7F32BC8D-16FF-4FA8-8A6C-ED39BDAA0414}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Queues.Tests.Unit", "src\HealthChecks.Azure.Queues\tests\NetEvolve.HealthChecks.Azure.Queues.Tests.Unit\NetEvolve.HealthChecks.Azure.Queues.Tests.Unit.csproj", "{0447505F-7AC2-46CA-A160-EBBE7DD2A454}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Queues.Tests.Integration", "src\HealthChecks.Azure.Queues\tests\NetEvolve.HealthChecks.Azure.Queues.Tests.Integration\NetEvolve.HealthChecks.Azure.Queues.Tests.Integration.csproj", "{C51B46F9-6B07-4954-94E5-02EB49E205D2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HealthChecks.Azure.Tables", "HealthChecks.Azure.Tables", "{59A16948-4C2C-4365-BEFE-DD629EC4A93C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0EC41369-D1ED-4188-93C8-581C236A0C82}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Tables", "src\HealthChecks.Azure.Tables\src\NetEvolve.HealthChecks.Azure.Tables\NetEvolve.HealthChecks.Azure.Tables.csproj", "{353F6973-0D78-46F3-AF1F-FDCFEC901D7F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BB1E06AF-4525-4211-A14F-C01A80F23D16}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Tables.Tests.Unit", "src\HealthChecks.Azure.Tables\tests\NetEvolve.HealthChecks.Azure.Tables.Tests.Unit\NetEvolve.HealthChecks.Azure.Tables.Tests.Unit.csproj", "{F12563B2-B05D-428F-96C5-DE9FAFB688F2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.HealthChecks.Azure.Tables.Tests.Integration", "src\HealthChecks.Azure.Tables\tests\NetEvolve.HealthChecks.Azure.Tables.Tests.Integration\NetEvolve.HealthChecks.Azure.Tables.Tests.Integration.csproj", "{E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -681,6 +717,114 @@ Global {21D5C2DB-3EC0-4B6A-B9CF-C3FED69168B7}.Release|x64.Build.0 = Release|Any CPU {21D5C2DB-3EC0-4B6A-B9CF-C3FED69168B7}.Release|x86.ActiveCfg = Release|Any CPU {21D5C2DB-3EC0-4B6A-B9CF-C3FED69168B7}.Release|x86.Build.0 = Release|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Debug|x64.ActiveCfg = Debug|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Debug|x64.Build.0 = Debug|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Debug|x86.ActiveCfg = Debug|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Debug|x86.Build.0 = Debug|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Release|Any CPU.Build.0 = Release|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Release|x64.ActiveCfg = Release|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Release|x64.Build.0 = Release|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Release|x86.ActiveCfg = Release|Any CPU + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5}.Release|x86.Build.0 = Release|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Debug|x64.ActiveCfg = Debug|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Debug|x64.Build.0 = Debug|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Debug|x86.ActiveCfg = Debug|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Debug|x86.Build.0 = Debug|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Release|Any CPU.Build.0 = Release|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Release|x64.ActiveCfg = Release|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Release|x64.Build.0 = Release|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Release|x86.ActiveCfg = Release|Any CPU + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3}.Release|x86.Build.0 = Release|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Debug|x64.ActiveCfg = Debug|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Debug|x64.Build.0 = Debug|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Debug|x86.ActiveCfg = Debug|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Debug|x86.Build.0 = Debug|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Release|Any CPU.Build.0 = Release|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Release|x64.ActiveCfg = Release|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Release|x64.Build.0 = Release|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Release|x86.ActiveCfg = Release|Any CPU + {33C2C63B-F1D8-4710-82CA-015667224F77}.Release|x86.Build.0 = Release|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Debug|Any CPU.Build.0 = Debug|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Debug|x64.ActiveCfg = Debug|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Debug|x64.Build.0 = Debug|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Debug|x86.ActiveCfg = Debug|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Debug|x86.Build.0 = Debug|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Release|Any CPU.ActiveCfg = Release|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Release|Any CPU.Build.0 = Release|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Release|x64.ActiveCfg = Release|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Release|x64.Build.0 = Release|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Release|x86.ActiveCfg = Release|Any CPU + {06089C14-C49F-4C4D-B690-05AEBC8FD822}.Release|x86.Build.0 = Release|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Debug|x64.ActiveCfg = Debug|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Debug|x64.Build.0 = Debug|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Debug|x86.ActiveCfg = Debug|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Debug|x86.Build.0 = Debug|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Release|Any CPU.Build.0 = Release|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Release|x64.ActiveCfg = Release|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Release|x64.Build.0 = Release|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Release|x86.ActiveCfg = Release|Any CPU + {0447505F-7AC2-46CA-A160-EBBE7DD2A454}.Release|x86.Build.0 = Release|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Debug|x64.ActiveCfg = Debug|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Debug|x64.Build.0 = Debug|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Debug|x86.ActiveCfg = Debug|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Debug|x86.Build.0 = Debug|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Release|Any CPU.Build.0 = Release|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Release|x64.ActiveCfg = Release|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Release|x64.Build.0 = Release|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Release|x86.ActiveCfg = Release|Any CPU + {C51B46F9-6B07-4954-94E5-02EB49E205D2}.Release|x86.Build.0 = Release|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Debug|x64.ActiveCfg = Debug|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Debug|x64.Build.0 = Debug|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Debug|x86.ActiveCfg = Debug|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Debug|x86.Build.0 = Debug|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Release|Any CPU.Build.0 = Release|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Release|x64.ActiveCfg = Release|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Release|x64.Build.0 = Release|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Release|x86.ActiveCfg = Release|Any CPU + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F}.Release|x86.Build.0 = Release|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Debug|x64.Build.0 = Debug|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Debug|x86.Build.0 = Debug|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Release|Any CPU.Build.0 = Release|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Release|x64.ActiveCfg = Release|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Release|x64.Build.0 = Release|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Release|x86.ActiveCfg = Release|Any CPU + {F12563B2-B05D-428F-96C5-DE9FAFB688F2}.Release|x86.Build.0 = Release|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Debug|x64.Build.0 = Debug|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Debug|x86.Build.0 = Debug|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Release|Any CPU.Build.0 = Release|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Release|x64.ActiveCfg = Release|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Release|x64.Build.0 = Release|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Release|x86.ActiveCfg = Release|Any CPU + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -770,6 +914,24 @@ Global {C3C6C388-BB80-484B-8264-9AEBDC732489} = {F88AC8B9-338D-496B-92A4-7D2872EA2887} {8A49D330-B135-4C07-97C7-5210F2F95673} = {C3C6C388-BB80-484B-8264-9AEBDC732489} {21D5C2DB-3EC0-4B6A-B9CF-C3FED69168B7} = {C3C6C388-BB80-484B-8264-9AEBDC732489} + {7D78D5FB-924A-45E1-9C53-D9AFEA9451AC} = {EF615D18-42E2-48A4-8EBA-E652DC574C56} + {F24F1664-82F5-4655-AD66-94A5331E11B7} = {7D78D5FB-924A-45E1-9C53-D9AFEA9451AC} + {15BB9AE6-EFC3-417C-90F9-0B223CF685B5} = {F24F1664-82F5-4655-AD66-94A5331E11B7} + {615B6749-5056-476C-BF10-E84807E5A15F} = {7D78D5FB-924A-45E1-9C53-D9AFEA9451AC} + {AD913B62-E270-4503-B9E1-1F69BEFB9CA3} = {615B6749-5056-476C-BF10-E84807E5A15F} + {33C2C63B-F1D8-4710-82CA-015667224F77} = {615B6749-5056-476C-BF10-E84807E5A15F} + {93C36BE6-1F4A-4672-841F-A3D56A5909B3} = {EF615D18-42E2-48A4-8EBA-E652DC574C56} + {51D0D687-7C08-4A6C-9060-C069476F46E1} = {93C36BE6-1F4A-4672-841F-A3D56A5909B3} + {06089C14-C49F-4C4D-B690-05AEBC8FD822} = {51D0D687-7C08-4A6C-9060-C069476F46E1} + {7F32BC8D-16FF-4FA8-8A6C-ED39BDAA0414} = {93C36BE6-1F4A-4672-841F-A3D56A5909B3} + {0447505F-7AC2-46CA-A160-EBBE7DD2A454} = {7F32BC8D-16FF-4FA8-8A6C-ED39BDAA0414} + {C51B46F9-6B07-4954-94E5-02EB49E205D2} = {7F32BC8D-16FF-4FA8-8A6C-ED39BDAA0414} + {59A16948-4C2C-4365-BEFE-DD629EC4A93C} = {EF615D18-42E2-48A4-8EBA-E652DC574C56} + {0EC41369-D1ED-4188-93C8-581C236A0C82} = {59A16948-4C2C-4365-BEFE-DD629EC4A93C} + {353F6973-0D78-46F3-AF1F-FDCFEC901D7F} = {0EC41369-D1ED-4188-93C8-581C236A0C82} + {BB1E06AF-4525-4211-A14F-C01A80F23D16} = {59A16948-4C2C-4365-BEFE-DD629EC4A93C} + {F12563B2-B05D-428F-96C5-DE9FAFB688F2} = {BB1E06AF-4525-4211-A14F-C01A80F23D16} + {E8CE9207-F19C-4FC2-8E21-A83E85F2E68A} = {BB1E06AF-4525-4211-A14F-C01A80F23D16} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {28B4CC2B-39E8-49C0-9687-78121BD83A53} @@ -778,6 +940,7 @@ Global src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{13c722e0-0bf9-41bf-baf9-2635044b6881}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{1d68abc3-be12-44fe-b1c0-08fd962af847}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{21d5c2db-3ec0-4b6a-b9cf-c3fed69168b7}*SharedItemsImports = 5 + src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{33c2c63b-f1d8-4710-82ca-015667224f77}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{36436743-dc06-4a42-93ff-56b165c51ea2}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{4d2bf995-9cc2-449b-b240-01c074217fa8}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{7ee322b9-2492-4250-b32e-09bc6ab00e14}*SharedItemsImports = 5 @@ -786,7 +949,9 @@ Global src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{8dbfda03-069e-4efa-83e7-275a6859227b}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{a9c0419e-0ff1-4f06-a677-f974c2525ec8}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{ad0ee9cb-86b7-4f46-90fc-15a1a01b8e56}*SharedItemsImports = 5 + src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{c51b46f9-6b07-4954-94e5-02eb49e205d2}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{ccd26d0d-5869-4727-9b62-31442d060adc}*SharedItemsImports = 5 src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{d2cf4d8b-dec3-433b-a130-5e5c465c2957}*SharedItemsImports = 5 + src\HealthChecks.Shared\tests\NetEvolve.HealthChecks.Shared.Tests\NetEvolve.HealthChecks.Shared.Tests.projitems*{e8ce9207-f19c-4fc2-8e21-a83e85f2e68a}*SharedItemsImports = 5 EndGlobalSection EndGlobal diff --git a/new-project.ps1 b/new-project.ps1 index 925bd0e5..1daa99cd 100644 --- a/new-project.ps1 +++ b/new-project.ps1 @@ -33,7 +33,7 @@ param ( [Parameter(Mandatory = $false)] [Switch] - $EnableProjectGrouping + $DisableArchitectureTests = $false ) . .\eng\scripts\new-project.ps1 @@ -47,4 +47,6 @@ New-Project ` -DisableIntegrationTests $DisableIntegrationTests ` -SolutionFile "./HealthChecks.sln" ` -OutputDirectory (Get-Location) ` - -EnableProjectGrouping $EnableProjectGrouping + -EnableProjectGrouping $true ` + -EnableAdvProjectGrouping $false ` + -DisableArchitectureTests $DisableArchitectureTests diff --git a/src/HealthChecks.Abstractions/src/NetEvolve.HealthChecks.Abstractions/IHealthChecksBuilderExtensions.cs b/src/HealthChecks.Abstractions/src/NetEvolve.HealthChecks.Abstractions/IHealthChecksBuilderExtensions.cs index bba1ed6e..177e6c9e 100644 --- a/src/HealthChecks.Abstractions/src/NetEvolve.HealthChecks.Abstractions/IHealthChecksBuilderExtensions.cs +++ b/src/HealthChecks.Abstractions/src/NetEvolve.HealthChecks.Abstractions/IHealthChecksBuilderExtensions.cs @@ -1,4 +1,4 @@ -namespace NetEvolve.HealthChecks; +namespace NetEvolve.HealthChecks.Abstractions; using System; using System.Collections.Generic; diff --git a/src/HealthChecks.Apache.Kafka/src/NetEvolve.HealthChecks.Apache.Kafka/DependencyInjectionExtensions.cs b/src/HealthChecks.Apache.Kafka/src/NetEvolve.HealthChecks.Apache.Kafka/DependencyInjectionExtensions.cs index 469b26e3..be1c2c68 100644 --- a/src/HealthChecks.Apache.Kafka/src/NetEvolve.HealthChecks.Apache.Kafka/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.Apache.Kafka/src/NetEvolve.HealthChecks.Apache.Kafka/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/DependencyInjectionExtensionsTests.cs b/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/DependencyInjectionExtensionsTests.cs index 15c0796f..c3d06fd5 100644 --- a/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/DependencyInjectionExtensionsTests.cs +++ b/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/DependencyInjectionExtensionsTests.cs @@ -22,8 +22,7 @@ public void AddKafka_WhenArgumentBuilderNull_ThrowArgumentNullException() void Act() => _ = builder.AddKafka("Test"); // Assert - var ex = Assert.Throws("builder", Act); - Assert.Equal("Value cannot be null. (Parameter 'builder')", ex.Message); + _ = Assert.Throws("builder", Act); } [Fact] @@ -39,8 +38,7 @@ public void AddKafka_WhenArgumentNameNull_ThrowArgumentNullException() void Act() => _ = builder.AddKafka(name); // Assert - var ex = Assert.Throws("name", Act); - Assert.Equal("Value cannot be null. (Parameter 'name')", ex.Message); + _ = Assert.Throws("name", Act); } [Fact] diff --git a/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/KafkaConfigureTests.cs b/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/KafkaConfigureTests.cs index 0aa7219a..ef09354b 100644 --- a/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/KafkaConfigureTests.cs +++ b/src/HealthChecks.Apache.Kafka/tests/NetEvolve.HealthChecks.Apache.Kafka.Tests.Unit/KafkaConfigureTests.cs @@ -131,8 +131,7 @@ public void Configure_WhenArgumentNameNull_ThrowArgumentNullException() void Act() => configure.Configure(name, options); // Assert - var ex = Assert.Throws("name", Act); - Assert.Equal("Value cannot be null. (Parameter 'name')", ex.Message); + _ = Assert.Throws("name", Act); } [Fact] diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobClientCreationMode.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobClientCreationMode.cs new file mode 100644 index 00000000..a1927448 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobClientCreationMode.cs @@ -0,0 +1,37 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using global::Azure.Identity; +using global::Azure.Storage.Blobs; + +/// +/// Describes the mode used to create the . +/// +public enum BlobClientCreationMode +{ + /// + /// The default mode. The is loading the preregistered instance from the . + /// + ServiceProvider = 0, + + /// + /// The is created using the . + /// + DefaultAzureCredentials = 1, + + /// + /// The is created using the . + /// + ConnectionString = 2, + + /// + /// The is created using the + /// and . As well as the . + /// + SharedKey = 3, + + /// + /// The is created using the . + /// + AzureSasCredential = 4 +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableConfigure.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableConfigure.cs new file mode 100644 index 00000000..adb4fac0 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableConfigure.cs @@ -0,0 +1,182 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using System.Threading; +using global::Azure.Storage.Blobs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NetEvolve.Arguments; +using static Microsoft.Extensions.Options.ValidateOptionsResult; + +internal sealed class BlobContainerAvailableConfigure + : IConfigureNamedOptions, + IValidateOptions +{ + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + + public BlobContainerAvailableConfigure( + IConfiguration configuration, + IServiceProvider serviceProvider + ) + { + _configuration = configuration; + _serviceProvider = serviceProvider; + } + + public void Configure(string? name, BlobContainerAvailableOptions options) + { + Argument.ThrowIfNullOrWhiteSpace(name); + _configuration.Bind($"HealthChecks:AzureBlobContainer:{name}", options); + } + + public void Configure(BlobContainerAvailableOptions options) => + Configure(Options.DefaultName, options); + + public ValidateOptionsResult Validate(string? name, BlobContainerAvailableOptions options) + { + if (string.IsNullOrWhiteSpace(name)) + { + return Fail("The name cannot be null or whitespace."); + } + + if (options is null) + { + return Fail("The option cannot be null."); + } + + if (options.Timeout < Timeout.Infinite) + { + return Fail("The timeout cannot be less than infinite (-1)."); + } + + if (string.IsNullOrWhiteSpace(options.ContainerName)) + { + return Fail("The container name cannot be null or whitespace."); + } + + var mode = options.Mode; + + return options.Mode switch + { + BlobClientCreationMode.ServiceProvider => ValidateModeServiceProvider(), + BlobClientCreationMode.ConnectionString => ValidateModeConnectionString(options), + BlobClientCreationMode.DefaultAzureCredentials + => ValidateModeDefaultAzureCredentials(options), + BlobClientCreationMode.SharedKey => ValidateModeSharedKey(options), + BlobClientCreationMode.AzureSasCredential => ValidateModeAzureSasCredential(options), + _ => Fail($"The mode `{mode}` is not supported."), + }; + } + + private static ValidateOptionsResult ValidateModeAzureSasCredential( + BlobContainerAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(BlobClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(BlobClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.ServiceUri.Query)) + { + return Fail( + $"The sas query token cannot be null or whitespace when using `{nameof(BlobClientCreationMode.AzureSasCredential)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeSharedKey( + BlobContainerAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountName)) + { + return Fail( + $"The account name cannot be null or whitespace when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountKey)) + { + return Fail( + $"The account key cannot be null or whitespace when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeDefaultAzureCredentials( + BlobContainerAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(BlobClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(BlobClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeConnectionString( + BlobContainerAvailableOptions options + ) + { + if (string.IsNullOrWhiteSpace(options.ConnectionString)) + { + return Fail( + $"The connection string cannot be null or whitespace when using `{nameof(BlobClientCreationMode.ConnectionString)}` mode." + ); + } + + return Success; + } + + private ValidateOptionsResult ValidateModeServiceProvider() + { + if (_serviceProvider.GetService() is null) + { + return Fail( + $"No service of type `{nameof(BlobServiceClient)}` registered. Please execute `builder.AddAzureClients()`." + ); + } + + return Success; + } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableHealthCheck.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableHealthCheck.cs new file mode 100644 index 00000000..d0d462ae --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableHealthCheck.cs @@ -0,0 +1,60 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using NetEvolve.Extensions.Tasks; +using NetEvolve.HealthChecks.Abstractions; + +internal sealed class BlobContainerAvailableHealthCheck + : ConfigurableHealthCheckBase +{ + private readonly IServiceProvider _serviceProvider; + + public BlobContainerAvailableHealthCheck( + IServiceProvider serviceProvider, + IOptionsMonitor optionsMonitor + ) + : base(optionsMonitor) => _serviceProvider = serviceProvider; + + protected override async ValueTask ExecuteHealthCheckAsync( + string name, + HealthStatus failureStatus, + BlobContainerAvailableOptions options, + CancellationToken cancellationToken + ) + { + var blobClient = ClientCreation.GetBlobServiceClient(name, options, _serviceProvider); + + var blobTask = blobClient + .GetBlobContainersAsync(cancellationToken: cancellationToken) + .AsPages(pageSizeHint: 1) + .GetAsyncEnumerator(cancellationToken) + .MoveNextAsync(); + + var (isValid, result) = await blobTask + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + var container = blobClient.GetBlobContainerClient(options.ContainerName); + + var containerExists = await container.ExistsAsync(cancellationToken).ConfigureAwait(false); + if (!containerExists) + { + return HealthCheckResult.Unhealthy( + $"{name}: Container `{options.ContainerName}` does not exist." + ); + } + + (var containerInTime, _) = await container + .GetPropertiesAsync(cancellationToken: cancellationToken) + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + return (isValid && result && containerInTime) + ? HealthCheckResult.Healthy($"{name}: Healthy") + : HealthCheckResult.Degraded($"{name}: Degraded"); + } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableOptions.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableOptions.cs new file mode 100644 index 00000000..694da1fd --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobContainerAvailableOptions.cs @@ -0,0 +1,50 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using global::Azure.Storage.Blobs; + +/// +/// Options for the . +/// +public sealed class BlobContainerAvailableOptions : IBlobOptions +{ + /// + /// Gets or sets the connection string. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the mode to create the client. + /// + public BlobClientCreationMode Mode { get; set; } + + /// + /// The timeout to use when connecting and executing tasks against database. + /// + public int Timeout { get; set; } = 100; + + /// + /// Gets or sets the name of the container. + /// + public string? ContainerName { get; set; } + + /// + /// Gets or sets the service uri. + /// + public Uri? ServiceUri { get; set; } + + /// + /// Gets or sets the account name. + /// + public string? AccountName { get; set; } + + /// + /// Gets or sets the account key. + /// + public string? AccountKey { get; set; } + + /// + /// Gets or sets the lambda to configure the . + /// + public Action? ConfigureClientOptions { get; set; } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableConfigure.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableConfigure.cs new file mode 100644 index 00000000..80618068 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableConfigure.cs @@ -0,0 +1,175 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using System.Threading; +using global::Azure.Storage.Blobs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NetEvolve.Arguments; +using static Microsoft.Extensions.Options.ValidateOptionsResult; + +internal sealed class BlobServiceAvailableConfigure + : IConfigureNamedOptions, + IValidateOptions +{ + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + + public BlobServiceAvailableConfigure( + IConfiguration configuration, + IServiceProvider serviceProvider + ) + { + _configuration = configuration; + _serviceProvider = serviceProvider; + } + + public void Configure(string? name, BlobServiceAvailableOptions options) + { + Argument.ThrowIfNullOrWhiteSpace(name); + _configuration.Bind($"HealthChecks:AzureBlob:{name}", options); + } + + public void Configure(BlobServiceAvailableOptions options) => + Configure(Options.DefaultName, options); + + public ValidateOptionsResult Validate(string? name, BlobServiceAvailableOptions options) + { + if (string.IsNullOrWhiteSpace(name)) + { + return Fail("The name cannot be null or whitespace."); + } + + if (options is null) + { + return Fail("The option cannot be null."); + } + + if (options.Timeout < Timeout.Infinite) + { + return Fail("The timeout cannot be less than infinite (-1)."); + } + + var mode = options.Mode; + + return options.Mode switch + { + BlobClientCreationMode.ServiceProvider => ValidateModeServiceProvider(), + BlobClientCreationMode.ConnectionString => ValidateModeConnectionString(options), + BlobClientCreationMode.DefaultAzureCredentials + => ValidateModeDefaultAzureCredentials(options), + BlobClientCreationMode.SharedKey => ValidateModeSharedKey(options), + BlobClientCreationMode.AzureSasCredential => ValidateModeAzureSasCredential(options), + _ => Fail($"The mode `{mode}` is not supported."), + }; + } + + private static ValidateOptionsResult ValidateModeAzureSasCredential( + BlobServiceAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(BlobClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(BlobClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.ServiceUri.Query)) + { + return Fail( + $"The sas query token cannot be null or whitespace when using `{nameof(BlobClientCreationMode.AzureSasCredential)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeSharedKey(BlobServiceAvailableOptions options) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountName)) + { + return Fail( + $"The account name cannot be null or whitespace when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountKey)) + { + return Fail( + $"The account key cannot be null or whitespace when using `{nameof(BlobClientCreationMode.SharedKey)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeDefaultAzureCredentials( + BlobServiceAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(BlobClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(BlobClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeConnectionString( + BlobServiceAvailableOptions options + ) + { + if (string.IsNullOrWhiteSpace(options.ConnectionString)) + { + return Fail( + $"The connection string cannot be null or whitespace when using `{nameof(BlobClientCreationMode.ConnectionString)}` mode." + ); + } + + return Success; + } + + private ValidateOptionsResult ValidateModeServiceProvider() + { + if (_serviceProvider.GetService() is null) + { + return Fail( + $"No service of type `{nameof(BlobServiceClient)}` registered. Please execute `builder.AddAzureClients()`." + ); + } + + return Success; + } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableHealthCheck.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableHealthCheck.cs new file mode 100644 index 00000000..6ba73cf2 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableHealthCheck.cs @@ -0,0 +1,45 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using NetEvolve.Extensions.Tasks; +using NetEvolve.HealthChecks.Abstractions; + +internal sealed class BlobServiceAvailableHealthCheck + : ConfigurableHealthCheckBase +{ + private readonly IServiceProvider _serviceProvider; + + public BlobServiceAvailableHealthCheck( + IServiceProvider serviceProvider, + IOptionsMonitor optionsMonitor + ) + : base(optionsMonitor) => _serviceProvider = serviceProvider; + + protected override async ValueTask ExecuteHealthCheckAsync( + string name, + HealthStatus failureStatus, + BlobServiceAvailableOptions options, + CancellationToken cancellationToken + ) + { + var blobClient = ClientCreation.GetBlobServiceClient(name, options, _serviceProvider); + + var blobTask = blobClient + .GetBlobContainersAsync(cancellationToken: cancellationToken) + .AsPages(pageSizeHint: 1) + .GetAsyncEnumerator(cancellationToken) + .MoveNextAsync(); + + var (isValid, result) = await blobTask + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + return (isValid && result) + ? HealthCheckResult.Healthy($"{name}: Healthy") + : HealthCheckResult.Degraded($"{name}: Degraded"); + } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableOptions.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableOptions.cs new file mode 100644 index 00000000..1e02c2c4 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/BlobServiceAvailableOptions.cs @@ -0,0 +1,45 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using global::Azure.Storage.Blobs; + +/// +/// Options for the . +/// +public sealed class BlobServiceAvailableOptions : IBlobOptions +{ + /// + /// Gets or sets the connection string. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the mode to create the client. + /// + public BlobClientCreationMode Mode { get; set; } + + /// + /// Gets or sets the timeout in milliseconds for executing the healthcheck. + /// + public int Timeout { get; set; } = 100; + + /// + /// Gets or sets the service uri. + /// + public Uri? ServiceUri { get; set; } + + /// + /// Gets or sets the account name. + /// + public string? AccountName { get; set; } + + /// + /// Gets or sets the account key. + /// + public string? AccountKey { get; set; } + + /// + /// Gets or sets the lambda to configure the . + /// + public Action? ConfigureClientOptions { get; set; } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/ClientCreation.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/ClientCreation.cs new file mode 100644 index 00000000..e8ac9b34 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/ClientCreation.cs @@ -0,0 +1,86 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using global::Azure; +using global::Azure.Core; +using global::Azure.Identity; +using global::Azure.Storage; +using global::Azure.Storage.Blobs; +using Microsoft.Extensions.DependencyInjection; + +internal static class ClientCreation +{ + private static ConcurrentDictionary? _blobServiceClients; + + internal static BlobServiceClient GetBlobServiceClient( + string name, + TOptions options, + IServiceProvider serviceProvider + ) + where TOptions : class, IBlobOptions + { + if (options.Mode == BlobClientCreationMode.ServiceProvider) + { + return serviceProvider.GetRequiredService(); + } + + if (_blobServiceClients is null) + { + _blobServiceClients = new ConcurrentDictionary( + StringComparer.OrdinalIgnoreCase + ); + } + + return _blobServiceClients.GetOrAdd( + name, + _ => CreateBlobServiceClient(options, serviceProvider) + ); + } + + internal static BlobServiceClient CreateBlobServiceClient( + TOptions options, + IServiceProvider serviceProvider + ) + where TOptions : class, IBlobOptions + { + BlobClientOptions? clientOptions = null; + if (options.ConfigureClientOptions is not null) + { + clientOptions = new BlobClientOptions(); + options.ConfigureClientOptions(clientOptions); + } + + switch (options.Mode) + { + case BlobClientCreationMode.DefaultAzureCredentials: + var tokenCredential = + serviceProvider.GetService() ?? new DefaultAzureCredential(); + return new BlobServiceClient(options.ServiceUri, tokenCredential, clientOptions); + case BlobClientCreationMode.ConnectionString: + return new BlobServiceClient(options.ConnectionString, clientOptions); + case BlobClientCreationMode.SharedKey: + var sharedKeyCredential = new StorageSharedKeyCredential( + options.AccountName, + options.AccountKey + ); + return new BlobServiceClient( + options.ServiceUri, + sharedKeyCredential, + clientOptions + ); + case BlobClientCreationMode.AzureSasCredential: + var blobUriBuilder = new BlobUriBuilder(options.ServiceUri) { Sas = null }; + var azureSasCredential = new AzureSasCredential(options.ServiceUri!.Query); + + return new BlobServiceClient( + blobUriBuilder.ToUri(), + azureSasCredential, + clientOptions + ); + default: + throw new UnreachableException($"Invalid client creation mode `{options.Mode}`."); + } + } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/DependencyInjectionExtensions.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/DependencyInjectionExtensions.cs new file mode 100644 index 00000000..a1098b09 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/DependencyInjectionExtensions.cs @@ -0,0 +1,117 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; + +/// +/// Extensions methods for with custom Health Checks. +/// +public static class DependencyInjectionExtensions +{ + private static readonly string[] _defaultTags = ["storage", "azure", "blob"]; + + /// + /// Adds a health check for the Azure Blob Storage, to check the availability of a named blob container. + /// + /// The . + /// The name of the . + /// An optional action to configure. + /// A list of additional tags that can be used to filter sets of health checks. Optional. + /// The is . + /// The is . + /// The is or whitespace. + /// The is already in use. + /// The is . + public static IHealthChecksBuilder AddBlobContainerAvailability( + [NotNull] this IHealthChecksBuilder builder, + [NotNull] string name, + Action? options = null, + params string[] tags + ) + { + ArgumentNullException.ThrowIfNull(builder); + Argument.ThrowIfNullOrEmpty(name); + ArgumentNullException.ThrowIfNull(tags); + + if (!builder.IsServiceTypeRegistered()) + { + _ = builder + .Services.AddSingleton() + .AddSingleton() + .ConfigureOptions(); + } + + if (builder.IsNameAlreadyUsed(name)) + { + throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null); + } + + if (options is not null) + { + _ = builder.Services.Configure(name, options); + } + + return builder.AddCheck( + name, + HealthStatus.Unhealthy, + _defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase) + ); + } + + /// + /// Adds a health check for the Azure Blob Storage, to check the availability of the blob service. + /// + /// The . + /// The name of the . + /// An optional action to configure. + /// A list of additional tags that can be used to filter sets of health checks. Optional. + /// The is . + /// The is . + /// The is or whitespace. + /// The is already in use. + /// The is . + public static IHealthChecksBuilder AddBlobServiceAvailability( + [NotNull] this IHealthChecksBuilder builder, + [NotNull] string name, + Action? options = null, + params string[] tags + ) + { + ArgumentNullException.ThrowIfNull(builder); + Argument.ThrowIfNullOrEmpty(name); + ArgumentNullException.ThrowIfNull(tags); + + if (!builder.IsServiceTypeRegistered()) + { + _ = builder + .Services.AddSingleton() + .AddSingleton() + .ConfigureOptions(); + } + + if (builder.IsNameAlreadyUsed(name)) + { + throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null); + } + + if (options is not null) + { + _ = builder.Services.Configure(name, options); + } + + return builder.AddCheck( + name, + HealthStatus.Unhealthy, + _defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase) + ); + } + + private sealed partial class AzureBlobContainerCheckMarker { } + + private sealed partial class AzureBlobServiceCheckMarker { } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/IBlobOptions.cs b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/IBlobOptions.cs new file mode 100644 index 00000000..2df9203f --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/IBlobOptions.cs @@ -0,0 +1,21 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs; + +using System; +using global::Azure.Storage.Blobs; + +internal interface IBlobOptions +{ + Uri? ServiceUri { get; } + + string? ConnectionString { get; } + + string? AccountName { get; } + + string? AccountKey { get; } + + BlobClientCreationMode Mode { get; } + + Action? ConfigureClientOptions { get; } + + int Timeout { get; } +} diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/NetEvolve.HealthChecks.Azure.Blobs.csproj b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/NetEvolve.HealthChecks.Azure.Blobs.csproj new file mode 100644 index 00000000..40537a9c --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/NetEvolve.HealthChecks.Azure.Blobs.csproj @@ -0,0 +1,25 @@ + + + + $(_ProjectTargetFrameworks) + + Contains HealthChecks for Azure Blob Storage. + $(PackageTags);azure;storage;blob; + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/README.md b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/README.md new file mode 100644 index 00000000..61a5b9d7 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/src/NetEvolve.HealthChecks.Azure.Blobs/README.md @@ -0,0 +1,75 @@ +# NetEvolve.HealthChecks.Azure.Blobs + +[![Nuget](https://img.shields.io/nuget/v/NetEvolve.HealthChecks.Azure.Blobs?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.Azure.Blobs/) +[![Nuget](https://img.shields.io/nuget/dt/NetEvolve.HealthChecks.Azure.Blobs?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.Azure.Blobs/) + +This package provides a health check for Azure Blobs, based on the [Azure.Storage.Blobs](https://www.nuget.org/packages/Azure.Storage.Blobs/) package. The main purpose is to check that the Azure Blob Service and the Storage Container is reachable and that the client can connect to it. + +:bulb: This package is available for .NET 6.0 and later. + +## Installation +To use this package, you need to add the package to your project. You can do this by using the NuGet package manager or by using the dotnet CLI. +```powershell +dotnet add package NetEvolve.HealthChecks.Azure.Blobs +``` + +## Health Check - Azure Blob Container Availability +The health check is a liveness check. It will check that the Azure Blob Service and the Storage Container is reachable and that the client can connect to it. If the service or the container needs longer than the configured timeout to respond, the health check will return `Degraded`. If the service or the container is not reachable, the health check will return `Unhealthy`. + +### Usage +After adding the package, yo need to import the namespace `NetEvolve.HealthChecks.Azure.Blobs` and add the health check to the service collection. +```csharp +using NetEvolve.HealthChecks.Azure.Blobs; +``` +Therefor you can use two different approaches. In both approaches you have to provide a name for the health check. + +### Parameters +- `name`: The name of the health check. The name is used to identify the configuration object. It is required and must be unique within the application. +- `options`: The configuration options for the health check. If you don't provide any options, the health check will use the configuration based approach. +- `tags`: The tags for the health check. The tags `azure`, `storage` and `blob` are always used as default and combined with the user input. You can provide additional tags to group or filter the health checks. + +### Variant 1: Configuration based +The first one is to use the configuration based approach. Therefor you have to add the configuration section `HealthChecks:AzureBlobContainer` to your `appsettings.json` file. +```csharp +var builder = services.AddHealthChecks(); + +builder.AddAzureBlobContainer(""); +``` + +The configuration looks like this: +```json +{ + ..., // other configuration + "HealthChecks": { + "AzureBlobContainer": { + "": { + "ConnectionString": "", // required + "ContainerName": "", // required + ..., // other configuration + "Timeout": "" // optional, default is 100 milliseconds + } + } + } +} +``` + +### Variant 2: Options based +The second one is to use the options based approach. Therefor you have to create an instance of `AzureBlobContainerOptions` and provide the configuration. +```csharp +var builder = services.AddHealthChecks(); + +builder.AddAzureBlobContainer("", options => +{ + options.ConnectionString = ""; + options.ContainerName = ""; + ... + options.Timeout = ""; +}); +``` + +### :bulb: You can always provide tags to all health checks, for grouping or filtering. + +```csharp +var builder = services.AddHealthChecks(); + builder.AddKafka("", options => ..., "azure"); +``` \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/AzuriteAccess.cs b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/AzuriteAccess.cs new file mode 100644 index 00000000..284ec7b9 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/AzuriteAccess.cs @@ -0,0 +1,28 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Testcontainers.Azurite; +using Xunit; + +[ExcludeFromCodeCoverage] +public sealed class AzuriteAccess : IAsyncLifetime, IAsyncDisposable +{ + public const string AccountName = "devstoreaccount1"; + public const string AccountKey = + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + + private readonly AzuriteContainer _container = new AzuriteBuilder() + .WithImage("mcr.microsoft.com/azure-storage/azurite:latest") + .Build(); + + public string ConnectionString => _container.GetConnectionString(); + + public async Task DisposeAsync() => await _container.DisposeAsync().ConfigureAwait(false); + + public async Task InitializeAsync() => await _container.StartAsync().ConfigureAwait(false); + + async ValueTask IAsyncDisposable.DisposeAsync() => + await _container.DisposeAsync().ConfigureAwait(false); +} diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/BlobContainerAvailableHealthCheckTests.cs b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/BlobContainerAvailableHealthCheckTests.cs new file mode 100644 index 00000000..4ec86ed9 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/BlobContainerAvailableHealthCheckTests.cs @@ -0,0 +1,240 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using global::Azure.Storage.Blobs; +using global::Azure.Storage.Sas; +using Microsoft.Extensions.Azure; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Azure.Blobs; +using NetEvolve.HealthChecks.Tests; +using Xunit; + +[IntegrationTest] +[ExcludeFromCodeCoverage] +[SetCulture("en-US")] +public class BlobContainerAvailableHealthCheckTests + : HealthCheckTestBase, + IClassFixture +{ + private readonly AzuriteAccess _container; + private readonly Uri _accountSasUri; + private readonly Uri _uriBlobStorage; + + public BlobContainerAvailableHealthCheckTests(AzuriteAccess container) + { + _container = container; + + var client = new BlobServiceClient(_container.ConnectionString); + _uriBlobStorage = client.Uri; + + _accountSasUri = client.GenerateAccountSasUri( + AccountSasPermissions.All, + DateTimeOffset.UtcNow.AddDays(1), + AccountSasResourceTypes.All + ); + + var containerClient = client.GetBlobContainerClient("test"); + + if (!containerClient.Exists()) + { + _ = containerClient.Create(); + } + } + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobServiceProviderHealthy", + options => + { + options.ContainerName = "test"; + options.Mode = BlobClientCreationMode.ServiceProvider; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddBlobServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobServiceProviderDegraded", + options => + { + options.ContainerName = "test"; + options.Mode = BlobClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddBlobServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobServiceProviderNotExistsUnhealthy", + options => + { + options.ContainerName = "notexists"; + options.Mode = BlobClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddBlobServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobServiceProviderWithConfigurationHealthy", + options => + { + options.ContainerName = "test"; + options.Mode = BlobClientCreationMode.ServiceProvider; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddBlobServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobConnectionStringHealthy", + options => + { + options.ContainerName = "test"; + options.ConnectionString = _container.ConnectionString; + options.Mode = BlobClientCreationMode.ConnectionString; + } + ); + }); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobConnectionStringDegraded", + options => + { + options.ContainerName = "test"; + options.ConnectionString = _container.ConnectionString; + options.Mode = BlobClientCreationMode.ConnectionString; + options.Timeout = 0; + } + ); + }); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobSharedKeyHealthy", + options => + { + options.ContainerName = "test"; + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = BlobClientCreationMode.SharedKey; + options.ServiceUri = _uriBlobStorage; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobSharedKeyDegraded", + options => + { + options.ContainerName = "test"; + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = BlobClientCreationMode.SharedKey; + options.Timeout = 0; + options.ServiceUri = _uriBlobStorage; + } + ); + }); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobAzureSasCredentialHealthy", + options => + { + options.ContainerName = "test"; + options.Mode = BlobClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + } + ); + }); + + [Fact] + public async Task AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobContainerAvailability( + "BlobAzureSasCredentialDegraded", + options => + { + options.ContainerName = "test"; + options.Mode = BlobClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + options.Timeout = 0; + } + ); + }); +} diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/BlobServiceAvailableHealthCheckTests.cs b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/BlobServiceAvailableHealthCheckTests.cs new file mode 100644 index 00000000..8d6c7317 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/BlobServiceAvailableHealthCheckTests.cs @@ -0,0 +1,201 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using global::Azure.Storage.Blobs; +using global::Azure.Storage.Sas; +using Microsoft.Extensions.Azure; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Azure.Blobs; +using NetEvolve.HealthChecks.Tests; +using Xunit; + +[IntegrationTest] +[ExcludeFromCodeCoverage] +[SetCulture("en-US")] +public class BlobServiceAvailableHealthCheckTests + : HealthCheckTestBase, + IClassFixture +{ + private readonly AzuriteAccess _container; + private readonly Uri _accountSasUri; + private readonly Uri _uriBlobStorage; + + public BlobServiceAvailableHealthCheckTests(AzuriteAccess container) + { + _container = container; + + var client = new BlobServiceClient(_container.ConnectionString); + _uriBlobStorage = client.Uri; + + _accountSasUri = client.GenerateAccountSasUri( + AccountSasPermissions.All, + DateTimeOffset.UtcNow.AddDays(1), + AccountSasResourceTypes.All + ); + } + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceServiceProviderHealthy", + options => + { + options.Mode = BlobClientCreationMode.ServiceProvider; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddBlobServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceServiceProviderWithConfigurationHealthy", + options => + { + options.Mode = BlobClientCreationMode.ServiceProvider; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddBlobServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceServiceProviderDegraded", + options => + { + options.Mode = BlobClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddBlobServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceConnectionStringHealthy", + options => + { + options.ConnectionString = _container.ConnectionString; + options.Mode = BlobClientCreationMode.ConnectionString; + } + ); + }); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceConnectionStringDegraded", + options => + { + options.ConnectionString = _container.ConnectionString; + options.Mode = BlobClientCreationMode.ConnectionString; + options.Timeout = 0; + } + ); + }); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceSharedKeyHealthy", + options => + { + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = BlobClientCreationMode.SharedKey; + options.ServiceUri = _uriBlobStorage; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceSharedKeyDegraded", + options => + { + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = BlobClientCreationMode.SharedKey; + options.Timeout = 0; + options.ServiceUri = _uriBlobStorage; + } + ); + }); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceAzureSasCredentialHealthy", + options => + { + options.Mode = BlobClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + } + ); + }); + + [Fact] + public async Task AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddBlobServiceAvailability( + "ServiceAzureSasCredentialDegraded", + options => + { + options.Mode = BlobClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + options.Timeout = 0; + } + ); + }); +} diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration.csproj b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration.csproj new file mode 100644 index 00000000..346f341f --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration.csproj @@ -0,0 +1,33 @@ + + + + $(_TestTargetFrameworks) + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..ac7f8ef1 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobServiceProviderWithConfigurationHealthy: Healthy, + name: BlobServiceProviderWithConfigurationHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..c1f3b98b --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobAzureSasCredentialDegraded: Degraded, + name: BlobAzureSasCredentialDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..964af190 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobAzureSasCredentialHealthy: Healthy, + name: BlobAzureSasCredentialHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..d099aecc --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobConnectionStringDegraded: Degraded, + name: BlobConnectionStringDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..5a0ef7a3 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobConnectionStringHealthy: Healthy, + name: BlobConnectionStringHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..5fd21ead --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobServiceProviderDegraded: Degraded, + name: BlobServiceProviderDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..524503f1 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobServiceProviderHealthy: Healthy, + name: BlobServiceProviderHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt new file mode 100644 index 00000000..a9be9b53 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobServiceProviderNotExistsUnhealthy: Container `notexists` does not exist., + name: BlobServiceProviderNotExistsUnhealthy, + status: Unhealthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Unhealthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..4aec1ae6 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobSharedKeyDegraded: Degraded, + name: BlobSharedKeyDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..280cfbfd --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobContainerAvailableHealthCheckTests.AddBlobContainerAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: BlobSharedKeyHealthy: Healthy, + name: BlobSharedKeyHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..fa60b9ca --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderWithConfigurationHealthy: Healthy, + name: ServiceServiceProviderWithConfigurationHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..602d23fa --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceAzureSasCredentialDegraded: Degraded, + name: ServiceAzureSasCredentialDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..5583abae --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceAzureSasCredentialHealthy: Healthy, + name: ServiceAzureSasCredentialHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..af622101 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceConnectionStringDegraded: Degraded, + name: ServiceConnectionStringDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..8f99a842 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceConnectionStringHealthy: Healthy, + name: ServiceConnectionStringHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..27abfc41 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderDegraded: Degraded, + name: ServiceServiceProviderDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..f8047be9 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderHealthy: Healthy, + name: ServiceServiceProviderHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..82e6127f --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceSharedKeyDegraded: Degraded, + name: ServiceSharedKeyDegraded, + status: Degraded, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..55038be2 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Integration/_snapshot/BlobServiceAvailableHealthCheckTests.AddBlobServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceSharedKeyHealthy: Healthy, + name: ServiceSharedKeyHealthy, + status: Healthy, + tags: [ + storage, + azure, + blob + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobContainerAvailableConfigureTests.cs b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobContainerAvailableConfigureTests.cs new file mode 100644 index 00000000..a436cf95 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobContainerAvailableConfigureTests.cs @@ -0,0 +1,263 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using global::Azure.Storage.Blobs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Azure.Blobs; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public sealed class BlobContainerAvailableConfigureTests +{ + [Fact] + public void Configue_OnlyOptions_ThrowsArgumentException() + { + // Arrange + var services = new ServiceCollection(); + var configure = new BlobContainerAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + var options = new BlobContainerAvailableOptions(); + + // Act / Assert + _ = Assert.Throws("name", () => configure.Configure(options)); + } + + [Theory] + [MemberData(nameof(GetValidateTestCases))] + public void Validate_Theory_Expected( + bool expectedResult, + string? expectedMessage, + string? name, + BlobContainerAvailableOptions options + ) + { + // Arrange + var services = new ServiceCollection(); + var configure = new BlobContainerAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + + // Act + var result = configure.Validate(name, options); + + // Assert + Assert.Equal(expectedResult, result.Succeeded); + Assert.Equal(expectedMessage, result.FailureMessage); + } + + public static TheoryData< + bool, + string?, + string?, + BlobContainerAvailableOptions + > GetValidateTestCases() + { + var data = new TheoryData + { + { false, "The name cannot be null or whitespace.", null, null! }, + { false, "The name cannot be null or whitespace.", "\t", null! }, + { false, "The option cannot be null.", "name", null! }, + { + false, + "The timeout cannot be less than infinite (-1).", + "name", + new BlobContainerAvailableOptions { Timeout = -2 } + }, + { + false, + "The container name cannot be null or whitespace.", + "name", + new BlobContainerAvailableOptions() + }, + { + false, + "The mode `13` is not supported.", + "name", + new BlobContainerAvailableOptions + { + Mode = (BlobClientCreationMode)13, + ContainerName = "test" + } + }, + // Mode: ServiceProvider + { + false, + $"No service of type `{nameof(BlobServiceClient)}` registered. Please execute `builder.AddAzureClients()`.", + "name", + new BlobContainerAvailableOptions + { + Mode = BlobClientCreationMode.ServiceProvider, + ContainerName = "test" + } + }, + // Mode: DefaultAzureCredentials + { + false, + "The service url cannot be null when using `DefaultAzureCredentials` mode.", + "name", + new BlobContainerAvailableOptions + { + Mode = BlobClientCreationMode.DefaultAzureCredentials, + ContainerName = "test" + } + }, + { + false, + "The service url must be an absolute url when using `DefaultAzureCredentials` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + true, + null, + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("https://example.com", UriKind.Absolute) + } + }, + // Mode: ConnectionString + { + false, + "The connection string cannot be null or whitespace when using `ConnectionString` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.ConnectionString + } + }, + { + true, + null, + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.ConnectionString, + ConnectionString = "connectionString" + } + }, + // Mode: SharedKey + { + false, + "The service url cannot be null when using `SharedKey` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.SharedKey + } + }, + { + false, + "The service url must be an absolute url when using `SharedKey` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The account name cannot be null or whitespace when using `SharedKey` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = null + } + }, + { + false, + "The account key cannot be null or whitespace when using `SharedKey` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = null + } + }, + { + true, + null, + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = "test" + } + }, + // Mode: AzureSasCredential + { + false, + "The service url cannot be null when using `AzureSasCredential` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.AzureSasCredential + } + }, + { + false, + "The service url must be an absolute url when using `AzureSasCredential` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The sas query token cannot be null or whitespace when using `AzureSasCredential` mode.", + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute", UriKind.Absolute) + } + }, + { + true, + null, + "name", + new BlobContainerAvailableOptions + { + ContainerName = "test", + Mode = BlobClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute?query=test", UriKind.Absolute) + } + } + }; + + return data; + } +} diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobContainerAvailableHealthCheckTests.cs b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobContainerAvailableHealthCheckTests.cs new file mode 100644 index 00000000..f10f5843 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobContainerAvailableHealthCheckTests.cs @@ -0,0 +1,23 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit; + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public class ClientCreationTests +{ + [Fact] + public void CreateBlobServiceClient_InvalidMode_ThrowUnreachableException() + { + var options = new BlobContainerAvailableOptions { Mode = (BlobClientCreationMode)13 }; + var serviceProvider = new ServiceCollection().BuildServiceProvider(); + + _ = Assert.Throws( + () => ClientCreation.CreateBlobServiceClient(options, serviceProvider) + ); + } +} diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobServiceAvailableConfigureTests.cs b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobServiceAvailableConfigureTests.cs new file mode 100644 index 00000000..2c945471 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/BlobServiceAvailableConfigureTests.cs @@ -0,0 +1,226 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using global::Azure.Storage.Blobs; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Azure.Blobs; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public sealed class BlobServiceAvailableConfigureTests +{ + [Fact] + public void Configue_OnlyOptions_ThrowsArgumentException() + { + // Arrange + var services = new ServiceCollection(); + var configure = new BlobServiceAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + var options = new BlobServiceAvailableOptions(); + + // Act / Assert + _ = Assert.Throws("name", () => configure.Configure(options)); + } + + [Theory] + [MemberData(nameof(GetValidateTestCases))] + public void Validate_Theory_Expected( + bool expectedResult, + string? expectedMessage, + string? name, + BlobServiceAvailableOptions options + ) + { + // Arrange + var services = new ServiceCollection(); + var configure = new BlobServiceAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + + // Act + var result = configure.Validate(name, options); + + // Assert + Assert.Equal(expectedResult, result.Succeeded); + Assert.Equal(expectedMessage, result.FailureMessage); + } + + public static TheoryData< + bool, + string?, + string?, + BlobServiceAvailableOptions + > GetValidateTestCases() + { + var data = new TheoryData + { + { false, "The name cannot be null or whitespace.", null, null! }, + { false, "The name cannot be null or whitespace.", "\t", null! }, + { false, "The option cannot be null.", "name", null! }, + { + false, + "The timeout cannot be less than infinite (-1).", + "name", + new BlobServiceAvailableOptions { Timeout = -2 } + }, + { + false, + "The mode `13` is not supported.", + "name", + new BlobServiceAvailableOptions { Mode = (BlobClientCreationMode)13 } + }, + // Mode: ServiceProvider + { + false, + $"No service of type `{nameof(BlobServiceClient)}` registered. Please execute `builder.AddAzureClients()`.", + "name", + new BlobServiceAvailableOptions { Mode = BlobClientCreationMode.ServiceProvider } + }, + // Mode: DefaultAzureCredentials + { + false, + "The service url cannot be null when using `DefaultAzureCredentials` mode.", + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.DefaultAzureCredentials + } + }, + { + false, + "The service url must be an absolute url when using `DefaultAzureCredentials` mode.", + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + true, + null, + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("https://example.com", UriKind.Absolute) + } + }, + // Mode: ConnectionString + { + false, + "The connection string cannot be null or whitespace when using `ConnectionString` mode.", + "name", + new BlobServiceAvailableOptions { Mode = BlobClientCreationMode.ConnectionString } + }, + { + true, + null, + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.ConnectionString, + ConnectionString = "connectionString" + } + }, + // Mode: SharedKey + { + false, + "The service url cannot be null when using `SharedKey` mode.", + "name", + new BlobServiceAvailableOptions { Mode = BlobClientCreationMode.SharedKey } + }, + { + false, + "The service url must be an absolute url when using `SharedKey` mode.", + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The account name cannot be null or whitespace when using `SharedKey` mode.", + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = null + } + }, + { + false, + "The account key cannot be null or whitespace when using `SharedKey` mode.", + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = null + } + }, + { + true, + null, + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = "test" + } + }, + // Mode: AzureSasCredential + { + false, + "The service url cannot be null when using `AzureSasCredential` mode.", + "name", + new BlobServiceAvailableOptions { Mode = BlobClientCreationMode.AzureSasCredential } + }, + { + false, + "The service url must be an absolute url when using `AzureSasCredential` mode.", + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The sas query token cannot be null or whitespace when using `AzureSasCredential` mode.", + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute", UriKind.Absolute) + } + }, + { + true, + null, + "name", + new BlobServiceAvailableOptions + { + Mode = BlobClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute?query=test", UriKind.Absolute) + } + } + }; + + return data; + } +} diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/DependencyInjectionExtensionsTests.cs b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/DependencyInjectionExtensionsTests.cs new file mode 100644 index 00000000..00b1bcde --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/DependencyInjectionExtensionsTests.cs @@ -0,0 +1,171 @@ +namespace NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public class DependencyInjectionExtensionsTests +{ + [Fact] + public void AddBlobContainerAvailability_WhenArgumentBuilderNull_ThrowArgumentNullException() + { + // Arrange + var builder = default(IHealthChecksBuilder); + + // Act + void Act() => _ = builder.AddBlobContainerAvailability("Test"); + + // Assert + _ = Assert.Throws("builder", Act); + } + + [Fact] + public void AddBlobContainerAvailability_WhenArgumentNameNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = default(string); + + // Act + void Act() => _ = builder.AddBlobContainerAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddBlobContainerAvailability_WhenArgumentNameEmpty_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = string.Empty; + + // Act + void Act() => _ = builder.AddBlobContainerAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddBlobContainerAvailability_WhenArgumentTagsNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var tags = default(string[]); + + // Act + void Act() => _ = builder.AddBlobContainerAvailability("Test", tags: tags); + + // Assert + _ = Assert.Throws("tags", Act); + } + + [Fact] + public void AddBlobContainerAvailability_WhenArgumentNameIsAlreadyUsed_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + const string name = "Test"; + + // Act + void Act() => + _ = builder + .AddBlobContainerAvailability(name, x => { }) + .AddBlobContainerAvailability(name); + + // Assert + _ = Assert.Throws(nameof(name), Act); + } + + [Fact] + public void AddBlobServiceAvailability_WhenArgumentBuilderNull_ThrowArgumentNullException() + { + // Arrange + var builder = default(IHealthChecksBuilder); + + // Act + void Act() => _ = builder.AddBlobServiceAvailability("Test"); + + // Assert + _ = Assert.Throws("builder", Act); + } + + [Fact] + public void AddBlobServiceAvailability_WhenArgumentNameNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = default(string); + + // Act + void Act() => _ = builder.AddBlobServiceAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddBlobServiceAvailability_WhenArgumentNameEmpty_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = string.Empty; + + // Act + void Act() => _ = builder.AddBlobServiceAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddBlobServiceAvailability_WhenArgumentTagsNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var tags = default(string[]); + + // Act + void Act() => _ = builder.AddBlobServiceAvailability("Test", tags: tags); + + // Assert + _ = Assert.Throws("tags", Act); + } + + [Fact] + public void AddBlobServiceAvailability_WhenArgumentNameIsAlreadyUsed_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + const string name = "Test"; + + // Act + void Act() => + _ = builder.AddBlobServiceAvailability(name, x => { }).AddBlobServiceAvailability(name); + + // Assert + _ = Assert.Throws(nameof(name), Act); + } +} diff --git a/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit.csproj b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit.csproj new file mode 100644 index 00000000..1eabd0d5 --- /dev/null +++ b/src/HealthChecks.Azure.Blobs/tests/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit/NetEvolve.HealthChecks.Azure.Blobs.Tests.Unit.csproj @@ -0,0 +1,27 @@ + + + + $(_TestTargetFrameworks) + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/ClientCreation.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/ClientCreation.cs new file mode 100644 index 00000000..01eb1d35 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/ClientCreation.cs @@ -0,0 +1,86 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using global::Azure; +using global::Azure.Core; +using global::Azure.Identity; +using global::Azure.Storage; +using global::Azure.Storage.Queues; +using Microsoft.Extensions.DependencyInjection; + +internal static class ClientCreation +{ + private static ConcurrentDictionary? _queueServiceClients; + + internal static QueueServiceClient GetQueueServiceClient( + string name, + TOptions options, + IServiceProvider serviceProvider + ) + where TOptions : class, IQueueOptions + { + if (options.Mode == QueueClientCreationMode.ServiceProvider) + { + return serviceProvider.GetRequiredService(); + } + + if (_queueServiceClients is null) + { + _queueServiceClients = new ConcurrentDictionary( + StringComparer.OrdinalIgnoreCase + ); + } + + return _queueServiceClients.GetOrAdd( + name, + _ => CreateQueueServiceClient(options, serviceProvider) + ); + } + + internal static QueueServiceClient CreateQueueServiceClient( + TOptions options, + IServiceProvider serviceProvider + ) + where TOptions : class, IQueueOptions + { + QueueClientOptions? clientOptions = null; + if (options.ConfigureClientOptions is not null) + { + clientOptions = new QueueClientOptions(); + options.ConfigureClientOptions(clientOptions); + } + + switch (options.Mode) + { + case QueueClientCreationMode.DefaultAzureCredentials: + var tokenCredential = + serviceProvider.GetService() ?? new DefaultAzureCredential(); + return new QueueServiceClient(options.ServiceUri, tokenCredential, clientOptions); + case QueueClientCreationMode.ConnectionString: + return new QueueServiceClient(options.ConnectionString, clientOptions); + case QueueClientCreationMode.SharedKey: + var sharedKeyCredential = new StorageSharedKeyCredential( + options.AccountName, + options.AccountKey + ); + return new QueueServiceClient( + options.ServiceUri, + sharedKeyCredential, + clientOptions + ); + case QueueClientCreationMode.AzureSasCredential: + var queueUriBuilder = new QueueUriBuilder(options.ServiceUri) { Sas = null }; + var azureSasCredential = new AzureSasCredential(options.ServiceUri!.Query); + + return new QueueServiceClient( + queueUriBuilder.ToUri(), + azureSasCredential, + clientOptions + ); + default: + throw new UnreachableException($"Invalid client creation mode `{options.Mode}`."); + } + } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/DependencyInjectionExtensions.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/DependencyInjectionExtensions.cs new file mode 100644 index 00000000..7b605429 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/DependencyInjectionExtensions.cs @@ -0,0 +1,117 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; + +/// +/// Extensions methods for with custom Health Checks. +/// +public static class DependencyInjectionExtensions +{ + private static readonly string[] _defaultTags = ["storage", "azure", "queue"]; + + /// + /// Adds a health check for the Azure Queue Storage, to check the availability of a named queue. + /// + /// The . + /// The name of the . + /// An optional action to configure. + /// A list of additional tags that can be used to filter sets of health checks. Optional. + /// The is . + /// The is . + /// The is or whitespace. + /// The is already in use. + /// The is . + public static IHealthChecksBuilder AddQueueClientAvailability( + [NotNull] this IHealthChecksBuilder builder, + [NotNull] string name, + Action? options = null, + params string[] tags + ) + { + ArgumentNullException.ThrowIfNull(builder); + Argument.ThrowIfNullOrEmpty(name); + ArgumentNullException.ThrowIfNull(tags); + + if (!builder.IsServiceTypeRegistered()) + { + _ = builder + .Services.AddSingleton() + .AddSingleton() + .ConfigureOptions(); + } + + if (builder.IsNameAlreadyUsed(name)) + { + throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null); + } + + if (options is not null) + { + _ = builder.Services.Configure(name, options); + } + + return builder.AddCheck( + name, + HealthStatus.Unhealthy, + _defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase) + ); + } + + /// + /// Adds a health check for the Azure Queue Storage, to check the availability of the queue service. + /// + /// The . + /// The name of the . + /// An optional action to configure. + /// A list of additional tags that can be used to filter sets of health checks. Optional. + /// The is . + /// The is . + /// The is or whitespace. + /// The is already in use. + /// The is . + public static IHealthChecksBuilder AddQueueServiceAvailability( + [NotNull] this IHealthChecksBuilder builder, + [NotNull] string name, + Action? options = null, + params string[] tags + ) + { + ArgumentNullException.ThrowIfNull(builder); + Argument.ThrowIfNullOrEmpty(name); + ArgumentNullException.ThrowIfNull(tags); + + if (!builder.IsServiceTypeRegistered()) + { + _ = builder + .Services.AddSingleton() + .AddSingleton() + .ConfigureOptions(); + } + + if (builder.IsNameAlreadyUsed(name)) + { + throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null); + } + + if (options is not null) + { + _ = builder.Services.Configure(name, options); + } + + return builder.AddCheck( + name, + HealthStatus.Unhealthy, + _defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase) + ); + } + + private sealed partial class AzureQueueClientCheckMarker { } + + private sealed partial class AzureQueueServiceCheckMarker { } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/IQueueOptions.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/IQueueOptions.cs new file mode 100644 index 00000000..8d91abef --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/IQueueOptions.cs @@ -0,0 +1,21 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using global::Azure.Storage.Queues; + +internal interface IQueueOptions +{ + Uri? ServiceUri { get; } + + string? ConnectionString { get; } + + string? AccountName { get; } + + string? AccountKey { get; } + + QueueClientCreationMode Mode { get; } + + Action? ConfigureClientOptions { get; } + + int Timeout { get; } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/NetEvolve.HealthChecks.Azure.Queues.csproj b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/NetEvolve.HealthChecks.Azure.Queues.csproj new file mode 100644 index 00000000..3ff05291 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/NetEvolve.HealthChecks.Azure.Queues.csproj @@ -0,0 +1,25 @@ + + + + $(_ProjectTargetFrameworks) + + Contains HealthChecks for Azure Queue Storage. + $(PackageTags);azure;storage;queue; + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableConfigure.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableConfigure.cs new file mode 100644 index 00000000..047537b4 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableConfigure.cs @@ -0,0 +1,180 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using System.Threading; +using global::Azure.Storage.Queues; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NetEvolve.Arguments; +using static Microsoft.Extensions.Options.ValidateOptionsResult; + +internal sealed class QueueClientAvailableConfigure + : IConfigureNamedOptions, + IValidateOptions +{ + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + + public QueueClientAvailableConfigure( + IConfiguration configuration, + IServiceProvider serviceProvider + ) + { + _configuration = configuration; + _serviceProvider = serviceProvider; + } + + public void Configure(string? name, QueueClientAvailableOptions options) + { + Argument.ThrowIfNullOrWhiteSpace(name); + _configuration.Bind($"HealthChecks:AzureQueueClient:{name}", options); + } + + public void Configure(QueueClientAvailableOptions options) => + Configure(Options.DefaultName, options); + + public ValidateOptionsResult Validate(string? name, QueueClientAvailableOptions options) + { + if (string.IsNullOrWhiteSpace(name)) + { + return Fail("The name cannot be null or whitespace."); + } + + if (options is null) + { + return Fail("The option cannot be null."); + } + + if (options.Timeout < Timeout.Infinite) + { + return Fail("The timeout cannot be less than infinite (-1)."); + } + + if (string.IsNullOrWhiteSpace(options.QueueName)) + { + return Fail("The queue name cannot be null or whitespace."); + } + + var mode = options.Mode; + + return options.Mode switch + { + QueueClientCreationMode.ServiceProvider => ValidateModeServiceProvider(), + QueueClientCreationMode.ConnectionString => ValidateModeConnectionString(options), + QueueClientCreationMode.DefaultAzureCredentials + => ValidateModeDefaultAzureCredentials(options), + QueueClientCreationMode.SharedKey => ValidateModeSharedKey(options), + QueueClientCreationMode.AzureSasCredential => ValidateModeAzureSasCredential(options), + _ => Fail($"The mode `{mode}` is not supported."), + }; + } + + private static ValidateOptionsResult ValidateModeAzureSasCredential( + QueueClientAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(QueueClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(QueueClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.ServiceUri.Query)) + { + return Fail( + $"The sas query token cannot be null or whitespace when using `{nameof(QueueClientCreationMode.AzureSasCredential)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeSharedKey(QueueClientAvailableOptions options) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountName)) + { + return Fail( + $"The account name cannot be null or whitespace when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountKey)) + { + return Fail( + $"The account key cannot be null or whitespace when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeDefaultAzureCredentials( + QueueClientAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(QueueClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(QueueClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeConnectionString( + QueueClientAvailableOptions options + ) + { + if (string.IsNullOrWhiteSpace(options.ConnectionString)) + { + return Fail( + $"The connection string cannot be null or whitespace when using `{nameof(QueueClientCreationMode.ConnectionString)}` mode." + ); + } + + return Success; + } + + private ValidateOptionsResult ValidateModeServiceProvider() + { + if (_serviceProvider.GetService() is null) + { + return Fail( + $"No service of type `{nameof(QueueServiceClient)}` registered. Please execute `builder.AddAzureClients()`." + ); + } + + return Success; + } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableHealthCheck.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableHealthCheck.cs new file mode 100644 index 00000000..9b6112f0 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableHealthCheck.cs @@ -0,0 +1,60 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using NetEvolve.Extensions.Tasks; +using NetEvolve.HealthChecks.Abstractions; + +internal sealed class QueueClientAvailableHealthCheck + : ConfigurableHealthCheckBase +{ + private readonly IServiceProvider _serviceProvider; + + public QueueClientAvailableHealthCheck( + IServiceProvider serviceProvider, + IOptionsMonitor optionsMonitor + ) + : base(optionsMonitor) => _serviceProvider = serviceProvider; + + protected override async ValueTask ExecuteHealthCheckAsync( + string name, + HealthStatus failureStatus, + QueueClientAvailableOptions options, + CancellationToken cancellationToken + ) + { + var queueClient = ClientCreation.GetQueueServiceClient(name, options, _serviceProvider); + + var queueTask = queueClient + .GetQueuesAsync(cancellationToken: cancellationToken) + .AsPages(pageSizeHint: 1) + .GetAsyncEnumerator(cancellationToken) + .MoveNextAsync(); + + var (isValid, result) = await queueTask + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + var queue = queueClient.GetQueueClient(options.QueueName); + + var queueExists = await queue.ExistsAsync(cancellationToken).ConfigureAwait(false); + if (!queueExists) + { + return HealthCheckResult.Unhealthy( + $"{name}: Queue `{options.QueueName}` does not exist." + ); + } + + (var queueInTime, _) = await queue + .GetPropertiesAsync(cancellationToken: cancellationToken) + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + return isValid && result && queueInTime + ? HealthCheckResult.Healthy($"{name}: Healthy") + : HealthCheckResult.Degraded($"{name}: Degraded"); + } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableOptions.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableOptions.cs new file mode 100644 index 00000000..2be2172f --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientAvailableOptions.cs @@ -0,0 +1,50 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using global::Azure.Storage.Queues; + +/// +/// Options for the . +/// +public sealed class QueueClientAvailableOptions : IQueueOptions +{ + /// + /// Gets or sets the connection string. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the mode to create the client. + /// + public QueueClientCreationMode Mode { get; set; } + + /// + /// The timeout to use when connecting and executing tasks against database. + /// + public int Timeout { get; set; } = 100; + + /// + /// Gets or sets the name of the queue. + /// + public string? QueueName { get; set; } + + /// + /// Gets or sets the service uri. + /// + public Uri? ServiceUri { get; set; } + + /// + /// Gets or sets the account name. + /// + public string? AccountName { get; set; } + + /// + /// Gets or sets the account key. + /// + public string? AccountKey { get; set; } + + /// + /// Gets or sets the lambda to configure the . + /// + public Action? ConfigureClientOptions { get; set; } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientCreationMode.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientCreationMode.cs new file mode 100644 index 00000000..c83bb351 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueClientCreationMode.cs @@ -0,0 +1,37 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using global::Azure.Identity; +using global::Azure.Storage.Queues; + +/// +/// Describes the mode used to create the . +/// +public enum QueueClientCreationMode +{ + /// + /// The default mode. The is loading the preregistered instance from the . + /// + ServiceProvider = 0, + + /// + /// The is created using the . + /// + DefaultAzureCredentials = 1, + + /// + /// The is created using the . + /// + ConnectionString = 2, + + /// + /// The is created using the + /// and . As well as the . + /// + SharedKey = 3, + + /// + /// The is created using the . + /// + AzureSasCredential = 4 +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableConfigure.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableConfigure.cs new file mode 100644 index 00000000..7c89d4d8 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableConfigure.cs @@ -0,0 +1,175 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using System.Threading; +using global::Azure.Storage.Queues; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NetEvolve.Arguments; +using static Microsoft.Extensions.Options.ValidateOptionsResult; + +internal sealed class QueueServiceAvailableConfigure + : IConfigureNamedOptions, + IValidateOptions +{ + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + + public QueueServiceAvailableConfigure( + IConfiguration configuration, + IServiceProvider serviceProvider + ) + { + _configuration = configuration; + _serviceProvider = serviceProvider; + } + + public void Configure(string? name, QueueServiceAvailableOptions options) + { + Argument.ThrowIfNullOrWhiteSpace(name); + _configuration.Bind($"HealthChecks:AzureQueueService:{name}", options); + } + + public void Configure(QueueServiceAvailableOptions options) => + Configure(Options.DefaultName, options); + + public ValidateOptionsResult Validate(string? name, QueueServiceAvailableOptions options) + { + if (string.IsNullOrWhiteSpace(name)) + { + return Fail("The name cannot be null or whitespace."); + } + + if (options is null) + { + return Fail("The option cannot be null."); + } + + if (options.Timeout < Timeout.Infinite) + { + return Fail("The timeout cannot be less than infinite (-1)."); + } + + var mode = options.Mode; + + return options.Mode switch + { + QueueClientCreationMode.ServiceProvider => ValidateModeServiceProvider(), + QueueClientCreationMode.ConnectionString => ValidateModeConnectionString(options), + QueueClientCreationMode.DefaultAzureCredentials + => ValidateModeDefaultAzureCredentials(options), + QueueClientCreationMode.SharedKey => ValidateModeSharedKey(options), + QueueClientCreationMode.AzureSasCredential => ValidateModeAzureSasCredential(options), + _ => Fail($"The mode `{mode}` is not supported."), + }; + } + + private static ValidateOptionsResult ValidateModeAzureSasCredential( + QueueServiceAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(QueueClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(QueueClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.ServiceUri.Query)) + { + return Fail( + $"The sas query token cannot be null or whitespace when using `{nameof(QueueClientCreationMode.AzureSasCredential)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeSharedKey(QueueServiceAvailableOptions options) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountName)) + { + return Fail( + $"The account name cannot be null or whitespace when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountKey)) + { + return Fail( + $"The account key cannot be null or whitespace when using `{nameof(QueueClientCreationMode.SharedKey)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeDefaultAzureCredentials( + QueueServiceAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(QueueClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(QueueClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeConnectionString( + QueueServiceAvailableOptions options + ) + { + if (string.IsNullOrWhiteSpace(options.ConnectionString)) + { + return Fail( + $"The connection string cannot be null or whitespace when using `{nameof(QueueClientCreationMode.ConnectionString)}` mode." + ); + } + + return Success; + } + + private ValidateOptionsResult ValidateModeServiceProvider() + { + if (_serviceProvider.GetService() is null) + { + return Fail( + $"No service of type `{nameof(QueueServiceClient)}` registered. Please execute `builder.AddAzureClients()`." + ); + } + + return Success; + } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableHealthCheck.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableHealthCheck.cs new file mode 100644 index 00000000..4bafc7a7 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableHealthCheck.cs @@ -0,0 +1,45 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using NetEvolve.Extensions.Tasks; +using NetEvolve.HealthChecks.Abstractions; + +internal sealed class QueueServiceAvailableHealthCheck + : ConfigurableHealthCheckBase +{ + private readonly IServiceProvider _serviceProvider; + + public QueueServiceAvailableHealthCheck( + IServiceProvider serviceProvider, + IOptionsMonitor optionsMonitor + ) + : base(optionsMonitor) => _serviceProvider = serviceProvider; + + protected override async ValueTask ExecuteHealthCheckAsync( + string name, + HealthStatus failureStatus, + QueueServiceAvailableOptions options, + CancellationToken cancellationToken + ) + { + var queueClient = ClientCreation.GetQueueServiceClient(name, options, _serviceProvider); + + var queueTask = queueClient + .GetQueuesAsync(cancellationToken: cancellationToken) + .AsPages(pageSizeHint: 1) + .GetAsyncEnumerator(cancellationToken) + .MoveNextAsync(); + + var (isValid, result) = await queueTask + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + return isValid && result + ? HealthCheckResult.Healthy($"{name}: Healthy") + : HealthCheckResult.Degraded($"{name}: Degraded"); + } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableOptions.cs b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableOptions.cs new file mode 100644 index 00000000..95e1c8b3 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/QueueServiceAvailableOptions.cs @@ -0,0 +1,45 @@ +namespace NetEvolve.HealthChecks.Azure.Queues; + +using System; +using global::Azure.Storage.Queues; + +/// +/// Options for the . +/// +public sealed class QueueServiceAvailableOptions : IQueueOptions +{ + /// + /// Gets or sets the connection string. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the mode to create the client. + /// + public QueueClientCreationMode Mode { get; set; } + + /// + /// Gets or sets the timeout in milliseconds for executing the healthcheck. + /// + public int Timeout { get; set; } = 100; + + /// + /// Gets or sets the service uri. + /// + public Uri? ServiceUri { get; set; } + + /// + /// Gets or sets the account name. + /// + public string? AccountName { get; set; } + + /// + /// Gets or sets the account key. + /// + public string? AccountKey { get; set; } + + /// + /// Gets or sets the lambda to configure the . + /// + public Action? ConfigureClientOptions { get; set; } +} diff --git a/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/README.md b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/README.md new file mode 100644 index 00000000..d88a9674 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/src/NetEvolve.HealthChecks.Azure.Queues/README.md @@ -0,0 +1,75 @@ +# NetEvolve.HealthChecks.Azure.Queues + +[![Nuget](https://img.shields.io/nuget/v/NetEvolve.HealthChecks.Azure.Queues?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.Azure.Queues/) +[![Nuget](https://img.shields.io/nuget/dt/NetEvolve.HealthChecks.Azure.Queues?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.Azure.Queues/) + +This package provides a health check for Azure Queues, based on the [Azure.Storage.Queues](https://www.nuget.org/packages/Azure.Storage.Queues/) package. The main purpose is to check that the Azure Queue Service is reachable and that the client can connect to it. + +:bulb: This package is available for .NET 6.0 and later. + +## Installation +To use this package, you need to add the package to your project. You can do this by using the NuGet package manager or by using the dotnet CLI. +```powershell +dotnet add package NetEvolve.HealthChecks.Azure.Queues +``` + +## Health Check - Azure Queue Client Availability +The health check is a liveness check. It will check that the Azure Queue Service is reachable and that the client can connect to it. If the service needs longer than the configured timeout to respond, the health check will return `Degraded`. If the service is not reachable, the health check will return `Unhealthy`. + +### Usage +After adding the package, yo need to import the namespace `NetEvolve.HealthChecks.Azure.Queues` and add the health check to the service collection. +```csharp +using NetEvolve.HealthChecks.Azure.Queues; +``` +Therefor you can use two different approaches. In both approaches you have to provide a name for the health check. + +### Parameters +- `name`: The name of the health check. The name is used to identify the configuration object. It is required and must be unique within the application. +- `options`: The configuration options for the health check. If you don't provide any options, the health check will use the configuration based approach. +- `tags`: The tags for the health check. The tags `azure`, `storage` and `blob` are always used as default and combined with the user input. You can provide additional tags to group or filter the health checks. + +### Variant 1: Configuration based +The first one is to use the configuration based approach. Therefor you have to add the configuration section `HealthChecks:AzureQueueClient` to your `appsettings.json` file. +```csharp +var builder = services.AddHealthChecks(); + +builder.AddAzureQueueClint(""); +``` + +The configuration looks like this: +```json +{ + ..., // other configuration + "HealthChecks": { + "AzureQueueClient": { + "": { + "ConnectionString": "", // required + "QueueName": "", // required + ..., // other configuration + "Timeout": "" // optional, default is 100 milliseconds + } + } + } +} +``` + +### Variant 2: Options based +The second one is to use the options based approach. Therefor you have to create an instance of `AzureQueueClientOptions` and provide the configuration. +```csharp +var builder = services.AddHealthChecks(); + +builder.AddAzureQueueClient("", options => +{ + options.ConnectionString = ""; + options.QueueName = ""; + ... + options.Timeout = ""; +}); +``` + +### :bulb: You can always provide tags to all health checks, for grouping or filtering. + +```csharp +var builder = services.AddHealthChecks(); + builder.AddKafka("", options => ..., "azure"); +``` \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/AzuriteAccess.cs b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/AzuriteAccess.cs new file mode 100644 index 00000000..f13e4392 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/AzuriteAccess.cs @@ -0,0 +1,28 @@ +namespace NetEvolve.HealthChecks.Azure.Queues.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Testcontainers.Azurite; +using Xunit; + +[ExcludeFromCodeCoverage] +public sealed class AzuriteAccess : IAsyncLifetime, IAsyncDisposable +{ + public const string AccountName = "devstoreaccount1"; + public const string AccountKey = + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + + private readonly AzuriteContainer _container = new AzuriteBuilder() + .WithImage("mcr.microsoft.com/azure-storage/azurite:latest") + .Build(); + + public string ConnectionString => _container.GetConnectionString(); + + public async Task DisposeAsync() => await _container.DisposeAsync().ConfigureAwait(false); + + public async Task InitializeAsync() => await _container.StartAsync().ConfigureAwait(false); + + async ValueTask IAsyncDisposable.DisposeAsync() => + await _container.DisposeAsync().ConfigureAwait(false); +} diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration.csproj b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration.csproj new file mode 100644 index 00000000..a4c0b3a2 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration.csproj @@ -0,0 +1,33 @@ + + + + $(_TestTargetFrameworks) + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/QueueClientAvailableHealthCheckTests.cs b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/QueueClientAvailableHealthCheckTests.cs new file mode 100644 index 00000000..23d6f096 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/QueueClientAvailableHealthCheckTests.cs @@ -0,0 +1,239 @@ +namespace NetEvolve.HealthChecks.Azure.Queues.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using global::Azure.Storage.Queues; +using global::Azure.Storage.Sas; +using Microsoft.Extensions.Azure; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Tests; +using Xunit; + +[IntegrationTest] +[ExcludeFromCodeCoverage] +[SetCulture("en-US")] +public class QueueClientAvailableHealthCheckTests + : HealthCheckTestBase, + IClassFixture +{ + private readonly AzuriteAccess _container; + private readonly Uri _accountSasUri; + private readonly Uri _uriQueueStorage; + + public QueueClientAvailableHealthCheckTests(AzuriteAccess container) + { + _container = container; + + var client = new QueueServiceClient(_container.ConnectionString); + _uriQueueStorage = client.Uri; + + _accountSasUri = client.GenerateAccountSasUri( + AccountSasPermissions.All, + DateTimeOffset.UtcNow.AddDays(1), + AccountSasResourceTypes.All + ); + + var queueClient = client.GetQueueClient("test"); + + if (!queueClient.Exists()) + { + _ = queueClient.Create(); + } + } + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueServiceProviderHealthy", + options => + { + options.QueueName = "test"; + options.Mode = QueueClientCreationMode.ServiceProvider; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddQueueServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueServiceProviderDegraded", + options => + { + options.QueueName = "test"; + options.Mode = QueueClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddQueueServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueServiceProviderNotExistsUnhealthy", + options => + { + options.QueueName = "notexists"; + options.Mode = QueueClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddQueueServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddQueueClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueServiceProviderWithConfigurationHealthy", + options => + { + options.QueueName = "test"; + options.Mode = QueueClientCreationMode.ServiceProvider; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddQueueServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueConnectionStringHealthy", + options => + { + options.QueueName = "test"; + options.ConnectionString = _container.ConnectionString; + options.Mode = QueueClientCreationMode.ConnectionString; + } + ); + }); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueConnectionStringDegraded", + options => + { + options.QueueName = "test"; + options.ConnectionString = _container.ConnectionString; + options.Mode = QueueClientCreationMode.ConnectionString; + options.Timeout = 0; + } + ); + }); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueSharedKeyHealthy", + options => + { + options.QueueName = "test"; + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = QueueClientCreationMode.SharedKey; + options.ServiceUri = _uriQueueStorage; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueSharedKeyDegraded", + options => + { + options.QueueName = "test"; + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = QueueClientCreationMode.SharedKey; + options.Timeout = 0; + options.ServiceUri = _uriQueueStorage; + } + ); + }); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueAzureSasCredentialHealthy", + options => + { + options.QueueName = "test"; + options.Mode = QueueClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + } + ); + }); + + [Fact] + public async Task AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueClientAvailability( + "QueueAzureSasCredentialDegraded", + options => + { + options.QueueName = "test"; + options.Mode = QueueClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + options.Timeout = 0; + } + ); + }); +} diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/QueueServiceAvailableHealthCheckTests.cs b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/QueueServiceAvailableHealthCheckTests.cs new file mode 100644 index 00000000..c1147b3f --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/QueueServiceAvailableHealthCheckTests.cs @@ -0,0 +1,200 @@ +namespace NetEvolve.HealthChecks.Azure.Queues.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using global::Azure.Storage.Queues; +using global::Azure.Storage.Sas; +using Microsoft.Extensions.Azure; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Tests; +using Xunit; + +[IntegrationTest] +[ExcludeFromCodeCoverage] +[SetCulture("en-US")] +public class QueueServiceAvailableHealthCheckTests + : HealthCheckTestBase, + IClassFixture +{ + private readonly AzuriteAccess _container; + private readonly Uri _accountSasUri; + private readonly Uri _uriQueueStorage; + + public QueueServiceAvailableHealthCheckTests(AzuriteAccess container) + { + _container = container; + + var client = new QueueServiceClient(_container.ConnectionString); + _uriQueueStorage = client.Uri; + + _accountSasUri = client.GenerateAccountSasUri( + AccountSasPermissions.All, + DateTimeOffset.UtcNow.AddDays(1), + AccountSasResourceTypes.All + ); + } + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceServiceProviderHealthy", + options => + { + options.Mode = QueueClientCreationMode.ServiceProvider; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddQueueServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceServiceProviderWithConfigurationHealthy", + options => + { + options.Mode = QueueClientCreationMode.ServiceProvider; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddQueueServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceServiceProviderDegraded", + options => + { + options.Mode = QueueClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddQueueServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceConnectionStringHealthy", + options => + { + options.ConnectionString = _container.ConnectionString; + options.Mode = QueueClientCreationMode.ConnectionString; + } + ); + }); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceConnectionStringDegraded", + options => + { + options.ConnectionString = _container.ConnectionString; + options.Mode = QueueClientCreationMode.ConnectionString; + options.Timeout = 0; + } + ); + }); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceSharedKeyHealthy", + options => + { + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = QueueClientCreationMode.SharedKey; + options.ServiceUri = _uriQueueStorage; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceSharedKeyDegraded", + options => + { + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = QueueClientCreationMode.SharedKey; + options.Timeout = 0; + options.ServiceUri = _uriQueueStorage; + } + ); + }); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceAzureSasCredentialHealthy", + options => + { + options.Mode = QueueClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + } + ); + }); + + [Fact] + public async Task AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddQueueServiceAvailability( + "ServiceAzureSasCredentialDegraded", + options => + { + options.Mode = QueueClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + options.Timeout = 0; + } + ); + }); +} diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..610046d3 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueServiceProviderWithConfigurationHealthy: Healthy, + name: QueueServiceProviderWithConfigurationHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..95b7915a --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueAzureSasCredentialDegraded: Degraded, + name: QueueAzureSasCredentialDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..25163018 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueAzureSasCredentialHealthy: Healthy, + name: QueueAzureSasCredentialHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..07daa1f0 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueConnectionStringDegraded: Degraded, + name: QueueConnectionStringDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..6e09fd55 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueConnectionStringHealthy: Healthy, + name: QueueConnectionStringHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..78a8b06e --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueServiceProviderDegraded: Degraded, + name: QueueServiceProviderDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..5095cba5 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueServiceProviderHealthy: Healthy, + name: QueueServiceProviderHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt new file mode 100644 index 00000000..e4b6e3ea --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueServiceProviderNotExistsUnhealthy: Queue `notexists` does not exist., + name: QueueServiceProviderNotExistsUnhealthy, + status: Unhealthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Unhealthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..ec9591ff --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueSharedKeyDegraded: Degraded, + name: QueueSharedKeyDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..bc26dca5 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueClientAvailableHealthCheckTests.AddQueueClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: QueueSharedKeyHealthy: Healthy, + name: QueueSharedKeyHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..02298764 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderWithConfigurationHealthy: Healthy, + name: ServiceServiceProviderWithConfigurationHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..cb78394f --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceAzureSasCredentialDegraded: Degraded, + name: ServiceAzureSasCredentialDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..e0d13989 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceAzureSasCredentialHealthy: Healthy, + name: ServiceAzureSasCredentialHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..1c60b313 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceConnectionStringDegraded: Degraded, + name: ServiceConnectionStringDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..0b0caad7 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceConnectionStringHealthy: Healthy, + name: ServiceConnectionStringHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..a413d2a0 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderDegraded: Degraded, + name: ServiceServiceProviderDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..38d7b25f --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderHealthy: Healthy, + name: ServiceServiceProviderHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..d9ddb8e8 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceSharedKeyDegraded: Degraded, + name: ServiceSharedKeyDegraded, + status: Degraded, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..715d4218 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Integration/_snapshot/QueueServiceAvailableHealthCheckTests.AddQueueServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceSharedKeyHealthy: Healthy, + name: ServiceSharedKeyHealthy, + status: Healthy, + tags: [ + storage, + azure, + queue + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/ClientCreationTests.cs b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/ClientCreationTests.cs new file mode 100644 index 00000000..ba8a2ad9 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/ClientCreationTests.cs @@ -0,0 +1,23 @@ +namespace NetEvolve.HealthChecks.Azure.Queues.Tests.Unit; + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public class ClientCreationTests +{ + [Fact] + public void CreateQueueServiceClient_InvalidMode_ThrowUnreachableException() + { + var options = new QueueClientAvailableOptions { Mode = (QueueClientCreationMode)13 }; + var serviceProvider = new ServiceCollection().BuildServiceProvider(); + + _ = Assert.Throws( + () => ClientCreation.CreateQueueServiceClient(options, serviceProvider) + ); + } +} diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/DependencyInjectionExtensionsTests.cs b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/DependencyInjectionExtensionsTests.cs new file mode 100644 index 00000000..43e16908 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/DependencyInjectionExtensionsTests.cs @@ -0,0 +1,171 @@ +namespace NetEvolve.HealthChecks.Azure.Queues.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public class DependencyInjectionExtensionsTests +{ + [Fact] + public void AddQueueClientAvailability_WhenArgumentBuilderNull_ThrowArgumentNullException() + { + // Arrange + var builder = default(IHealthChecksBuilder); + + // Act + void Act() => _ = builder.AddQueueClientAvailability("Test"); + + // Assert + _ = Assert.Throws("builder", Act); + } + + [Fact] + public void AddQueueClientAvailability_WhenArgumentNameNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = default(string); + + // Act + void Act() => _ = builder.AddQueueClientAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddQueueClientAvailability_WhenArgumentNameEmpty_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = string.Empty; + + // Act + void Act() => _ = builder.AddQueueClientAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddQueueClientAvailability_WhenArgumentTagsNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var tags = default(string[]); + + // Act + void Act() => _ = builder.AddQueueClientAvailability("Test", tags: tags); + + // Assert + _ = Assert.Throws("tags", Act); + } + + [Fact] + public void AddQueueClientAvailability_WhenArgumentNameIsAlreadyUsed_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + const string name = "Test"; + + // Act + void Act() => + _ = builder.AddQueueClientAvailability(name, x => { }).AddQueueClientAvailability(name); + + // Assert + _ = Assert.Throws(nameof(name), Act); + } + + [Fact] + public void AddQueueServiceAvailability_WhenArgumentBuilderNull_ThrowArgumentNullException() + { + // Arrange + var builder = default(IHealthChecksBuilder); + + // Act + void Act() => _ = builder.AddQueueServiceAvailability("Test"); + + // Assert + _ = Assert.Throws("builder", Act); + } + + [Fact] + public void AddQueueServiceAvailability_WhenArgumentNameNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = default(string); + + // Act + void Act() => _ = builder.AddQueueServiceAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddQueueServiceAvailability_WhenArgumentNameEmpty_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = string.Empty; + + // Act + void Act() => _ = builder.AddQueueServiceAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddQueueServiceAvailability_WhenArgumentTagsNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var tags = default(string[]); + + // Act + void Act() => _ = builder.AddQueueServiceAvailability("Test", tags: tags); + + // Assert + _ = Assert.Throws("tags", Act); + } + + [Fact] + public void AddQueueServiceAvailability_WhenArgumentNameIsAlreadyUsed_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + const string name = "Test"; + + // Act + void Act() => + _ = builder + .AddQueueServiceAvailability(name, x => { }) + .AddQueueServiceAvailability(name); + + // Assert + _ = Assert.Throws(nameof(name), Act); + } +} diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit.csproj b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit.csproj new file mode 100644 index 00000000..c42ec1a9 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit.csproj @@ -0,0 +1,27 @@ + + + + $(_TestTargetFrameworks) + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/QueueClientAvailableConfigureTests.cs b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/QueueClientAvailableConfigureTests.cs new file mode 100644 index 00000000..c81693d2 --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/QueueClientAvailableConfigureTests.cs @@ -0,0 +1,262 @@ +namespace NetEvolve.HealthChecks.Azure.Queues.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using global::Azure.Storage.Queues; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public sealed class QueueClientAvailableConfigureTests +{ + [Fact] + public void Configue_OnlyOptions_ThrowsArgumentException() + { + // Arrange + var services = new ServiceCollection(); + var configure = new QueueClientAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + var options = new QueueClientAvailableOptions(); + + // Act / Assert + _ = Assert.Throws("name", () => configure.Configure(options)); + } + + [Theory] + [MemberData(nameof(GetValidateTestCases))] + public void Validate_Theory_Expected( + bool expectedResult, + string? expectedMessage, + string? name, + QueueClientAvailableOptions options + ) + { + // Arrange + var services = new ServiceCollection(); + var configure = new QueueClientAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + + // Act + var result = configure.Validate(name, options); + + // Assert + Assert.Equal(expectedResult, result.Succeeded); + Assert.Equal(expectedMessage, result.FailureMessage); + } + + public static TheoryData< + bool, + string?, + string?, + QueueClientAvailableOptions + > GetValidateTestCases() + { + var data = new TheoryData + { + { false, "The name cannot be null or whitespace.", null, null! }, + { false, "The name cannot be null or whitespace.", "\t", null! }, + { false, "The option cannot be null.", "name", null! }, + { + false, + "The timeout cannot be less than infinite (-1).", + "name", + new QueueClientAvailableOptions { Timeout = -2 } + }, + { + false, + "The queue name cannot be null or whitespace.", + "name", + new QueueClientAvailableOptions() + }, + { + false, + "The mode `13` is not supported.", + "name", + new QueueClientAvailableOptions + { + Mode = (QueueClientCreationMode)13, + QueueName = "test" + } + }, + // Mode: ServiceProvider + { + false, + $"No service of type `{nameof(QueueServiceClient)}` registered. Please execute `builder.AddAzureClients()`.", + "name", + new QueueClientAvailableOptions + { + Mode = QueueClientCreationMode.ServiceProvider, + QueueName = "test" + } + }, + // Mode: DefaultAzureCredentials + { + false, + "The service url cannot be null when using `DefaultAzureCredentials` mode.", + "name", + new QueueClientAvailableOptions + { + Mode = QueueClientCreationMode.DefaultAzureCredentials, + QueueName = "test" + } + }, + { + false, + "The service url must be an absolute url when using `DefaultAzureCredentials` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + true, + null, + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("https://example.com", UriKind.Absolute) + } + }, + // Mode: ConnectionString + { + false, + "The connection string cannot be null or whitespace when using `ConnectionString` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.ConnectionString + } + }, + { + true, + null, + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.ConnectionString, + ConnectionString = "connectionString" + } + }, + // Mode: SharedKey + { + false, + "The service url cannot be null when using `SharedKey` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.SharedKey + } + }, + { + false, + "The service url must be an absolute url when using `SharedKey` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The account name cannot be null or whitespace when using `SharedKey` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = null + } + }, + { + false, + "The account key cannot be null or whitespace when using `SharedKey` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = null + } + }, + { + true, + null, + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = "test" + } + }, + // Mode: AzureSasCredential + { + false, + "The service url cannot be null when using `AzureSasCredential` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.AzureSasCredential + } + }, + { + false, + "The service url must be an absolute url when using `AzureSasCredential` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The sas query token cannot be null or whitespace when using `AzureSasCredential` mode.", + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute", UriKind.Absolute) + } + }, + { + true, + null, + "name", + new QueueClientAvailableOptions + { + QueueName = "test", + Mode = QueueClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute?query=test", UriKind.Absolute) + } + } + }; + + return data; + } +} diff --git a/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/QueueServiceAvailableConfigureTests.cs b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/QueueServiceAvailableConfigureTests.cs new file mode 100644 index 00000000..24c5462e --- /dev/null +++ b/src/HealthChecks.Azure.Queues/tests/NetEvolve.HealthChecks.Azure.Queues.Tests.Unit/QueueServiceAvailableConfigureTests.cs @@ -0,0 +1,228 @@ +namespace NetEvolve.HealthChecks.Azure.Queues.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using global::Azure.Storage.Queues; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public sealed class QueueServiceAvailableConfigureTests +{ + [Fact] + public void Configue_OnlyOptions_ThrowsArgumentException() + { + // Arrange + var services = new ServiceCollection(); + var configure = new QueueServiceAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + var options = new QueueServiceAvailableOptions(); + + // Act / Assert + _ = Assert.Throws("name", () => configure.Configure(options)); + } + + [Theory] + [MemberData(nameof(GetValidateTestCases))] + public void Validate_Theory_Expected( + bool expectedResult, + string? expectedMessage, + string? name, + QueueServiceAvailableOptions options + ) + { + // Arrange + var services = new ServiceCollection(); + var configure = new QueueServiceAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + + // Act + var result = configure.Validate(name, options); + + // Assert + Assert.Equal(expectedResult, result.Succeeded); + Assert.Equal(expectedMessage, result.FailureMessage); + } + + public static TheoryData< + bool, + string?, + string?, + QueueServiceAvailableOptions + > GetValidateTestCases() + { + var data = new TheoryData + { + { false, "The name cannot be null or whitespace.", null, null! }, + { false, "The name cannot be null or whitespace.", "\t", null! }, + { false, "The option cannot be null.", "name", null! }, + { + false, + "The timeout cannot be less than infinite (-1).", + "name", + new QueueServiceAvailableOptions { Timeout = -2 } + }, + { + false, + "The mode `13` is not supported.", + "name", + new QueueServiceAvailableOptions { Mode = (QueueClientCreationMode)13 } + }, + // Mode: ServiceProvider + { + false, + $"No service of type `{nameof(QueueServiceClient)}` registered. Please execute `builder.AddAzureClients()`.", + "name", + new QueueServiceAvailableOptions { Mode = QueueClientCreationMode.ServiceProvider } + }, + // Mode: DefaultAzureCredentials + { + false, + "The service url cannot be null when using `DefaultAzureCredentials` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.DefaultAzureCredentials + } + }, + { + false, + "The service url must be an absolute url when using `DefaultAzureCredentials` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + true, + null, + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("https://example.com", UriKind.Absolute) + } + }, + // Mode: ConnectionString + { + false, + "The connection string cannot be null or whitespace when using `ConnectionString` mode.", + "name", + new QueueServiceAvailableOptions { Mode = QueueClientCreationMode.ConnectionString } + }, + { + true, + null, + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.ConnectionString, + ConnectionString = "connectionString" + } + }, + // Mode: SharedKey + { + false, + "The service url cannot be null when using `SharedKey` mode.", + "name", + new QueueServiceAvailableOptions { Mode = QueueClientCreationMode.SharedKey } + }, + { + false, + "The service url must be an absolute url when using `SharedKey` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The account name cannot be null or whitespace when using `SharedKey` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = null + } + }, + { + false, + "The account key cannot be null or whitespace when using `SharedKey` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = null + } + }, + { + true, + null, + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = "test" + } + }, + // Mode: AzureSasCredential + { + false, + "The service url cannot be null when using `AzureSasCredential` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.AzureSasCredential + } + }, + { + false, + "The service url must be an absolute url when using `AzureSasCredential` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The sas query token cannot be null or whitespace when using `AzureSasCredential` mode.", + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute", UriKind.Absolute) + } + }, + { + true, + null, + "name", + new QueueServiceAvailableOptions + { + Mode = QueueClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute?query=test", UriKind.Absolute) + } + } + }; + + return data; + } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/ClientCreation.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/ClientCreation.cs new file mode 100644 index 00000000..a86b7602 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/ClientCreation.cs @@ -0,0 +1,85 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using global::Azure; +using global::Azure.Core; +using global::Azure.Data.Tables; +using global::Azure.Identity; +using Microsoft.Extensions.DependencyInjection; + +internal static class ClientCreation +{ + private static ConcurrentDictionary? _tableServiceClients; + + internal static TableServiceClient GetTableServiceClient( + string name, + TOptions options, + IServiceProvider serviceProvider + ) + where TOptions : class, ITableOptions + { + if (options.Mode == TableClientCreationMode.ServiceProvider) + { + return serviceProvider.GetRequiredService(); + } + + if (_tableServiceClients is null) + { + _tableServiceClients = new ConcurrentDictionary( + StringComparer.OrdinalIgnoreCase + ); + } + + return _tableServiceClients.GetOrAdd( + name, + _ => CreateTableServiceClient(options, serviceProvider) + ); + } + + internal static TableServiceClient CreateTableServiceClient( + TOptions options, + IServiceProvider serviceProvider + ) + where TOptions : class, ITableOptions + { + TableClientOptions? clientOptions = null; + if (options.ConfigureClientOptions is not null) + { + clientOptions = new TableClientOptions(); + options.ConfigureClientOptions(clientOptions); + } + + switch (options.Mode) + { + case TableClientCreationMode.DefaultAzureCredentials: + var tokenCredential = + serviceProvider.GetService() ?? new DefaultAzureCredential(); + return new TableServiceClient(options.ServiceUri, tokenCredential, clientOptions); + case TableClientCreationMode.ConnectionString: + return new TableServiceClient(options.ConnectionString, clientOptions); + case TableClientCreationMode.SharedKey: + var sharedKeyCredential = new TableSharedKeyCredential( + options.AccountName, + options.AccountKey + ); + return new TableServiceClient( + options.ServiceUri, + sharedKeyCredential, + clientOptions + ); + case TableClientCreationMode.AzureSasCredential: + var tableUriBuilder = new TableUriBuilder(options.ServiceUri) { Sas = null }; + var azureSasCredential = new AzureSasCredential(options.ServiceUri!.Query); + + return new TableServiceClient( + tableUriBuilder.ToUri(), + azureSasCredential, + clientOptions + ); + default: + throw new UnreachableException($"Invalid client creation mode `{options.Mode}`."); + } + } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/DependencyInjectionExtensions.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/DependencyInjectionExtensions.cs new file mode 100644 index 00000000..bd7ce52d --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/DependencyInjectionExtensions.cs @@ -0,0 +1,117 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; + +/// +/// Extensions methods for with custom Health Checks. +/// +public static class DependencyInjectionExtensions +{ + private static readonly string[] _defaultTags = ["storage", "azure", "table"]; + + /// + /// Adds a health check for the Azure Table Storage, to check the availability of a named table container. + /// + /// The . + /// The name of the . + /// An optional action to configure. + /// A list of additional tags that can be used to filter sets of health checks. Optional. + /// The is . + /// The is . + /// The is or whitespace. + /// The is already in use. + /// The is . + public static IHealthChecksBuilder AddTableClientAvailability( + [NotNull] this IHealthChecksBuilder builder, + [NotNull] string name, + Action? options = null, + params string[] tags + ) + { + ArgumentNullException.ThrowIfNull(builder); + Argument.ThrowIfNullOrEmpty(name); + ArgumentNullException.ThrowIfNull(tags); + + if (!builder.IsServiceTypeRegistered()) + { + _ = builder + .Services.AddSingleton() + .AddSingleton() + .ConfigureOptions(); + } + + if (builder.IsNameAlreadyUsed(name)) + { + throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null); + } + + if (options is not null) + { + _ = builder.Services.Configure(name, options); + } + + return builder.AddCheck( + name, + HealthStatus.Unhealthy, + _defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase) + ); + } + + /// + /// Adds a health check for the Azure Table Storage, to check the availability of the table service. + /// + /// The . + /// The name of the . + /// An optional action to configure. + /// A list of additional tags that can be used to filter sets of health checks. Optional. + /// The is . + /// The is . + /// The is or whitespace. + /// The is already in use. + /// The is . + public static IHealthChecksBuilder AddTableServiceAvailability( + [NotNull] this IHealthChecksBuilder builder, + [NotNull] string name, + Action? options = null, + params string[] tags + ) + { + ArgumentNullException.ThrowIfNull(builder); + Argument.ThrowIfNullOrEmpty(name); + ArgumentNullException.ThrowIfNull(tags); + + if (!builder.IsServiceTypeRegistered()) + { + _ = builder + .Services.AddSingleton() + .AddSingleton() + .ConfigureOptions(); + } + + if (builder.IsNameAlreadyUsed(name)) + { + throw new ArgumentException($"Name `{name}` already in use.", nameof(name), null); + } + + if (options is not null) + { + _ = builder.Services.Configure(name, options); + } + + return builder.AddCheck( + name, + HealthStatus.Unhealthy, + _defaultTags.Union(tags, StringComparer.OrdinalIgnoreCase) + ); + } + + private sealed partial class AzureTableCheckMarker { } + + private sealed partial class AzureTableServiceCheckMarker { } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/IQueueOptions.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/IQueueOptions.cs new file mode 100644 index 00000000..df246e07 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/IQueueOptions.cs @@ -0,0 +1,21 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using global::Azure.Data.Tables; + +internal interface ITableOptions +{ + Uri? ServiceUri { get; } + + string? ConnectionString { get; } + + string? AccountName { get; } + + string? AccountKey { get; } + + TableClientCreationMode Mode { get; } + + Action? ConfigureClientOptions { get; } + + int Timeout { get; } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/NetEvolve.HealthChecks.Azure.Tables.csproj b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/NetEvolve.HealthChecks.Azure.Tables.csproj new file mode 100644 index 00000000..8eadff4c --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/NetEvolve.HealthChecks.Azure.Tables.csproj @@ -0,0 +1,33 @@ + + + + $(_ProjectTargetFrameworks) + + Contains HealthChecks for Azure Table Storage. + $(PackageTags);azure;data;table; + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + true + true + + + + diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/README.md b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/README.md new file mode 100644 index 00000000..86cf0b17 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/README.md @@ -0,0 +1,75 @@ +# NetEvolve.HealthChecks.Azure.Tables + +[![Nuget](https://img.shields.io/nuget/v/NetEvolve.HealthChecks.Azure.Tables?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.Azure.Tables/) +[![Nuget](https://img.shields.io/nuget/dt/NetEvolve.HealthChecks.Azure.Tables?logo=nuget)](https://www.nuget.org/packages/NetEvolve.HealthChecks.Azure.Tables/) + +This package provides a health check for Azure Tables, based on the [Azure.Storage.Tables](https://www.nuget.org/packages/Azure.Storage.Tables/) package. The main purpose is to check that the Azure Table Service is reachable and that the client can connect to it. + +:bulb: This package is available for .NET 6.0 and later. + +## Installation +To use this package, you need to add the package to your project. You can do this by using the NuGet package manager or by using the dotnet CLI. +```powershell +dotnet add package NetEvolve.HealthChecks.Azure.Tables +``` + +## Health Check - Azure Table Client Availability +The health check is a liveness check. It will check that the Azure Table Service is reachable and that the client can connect to it. If the service needs longer than the configured timeout to respond, the health check will return `Degraded`. If the service is not reachable, the health check will return `Unhealthy`. + +### Usage +After adding the package, yo need to import the namespace `NetEvolve.HealthChecks.Azure.Tables` and add the health check to the service collection. +```csharp +using NetEvolve.HealthChecks.Azure.Tables; +``` +Therefor you can use two different approaches. In both approaches you have to provide a name for the health check. + +### Parameters +- `name`: The name of the health check. The name is used to identify the configuration object. It is required and must be unique within the application. +- `options`: The configuration options for the health check. If you don't provide any options, the health check will use the configuration based approach. +- `tags`: The tags for the health check. The tags `azure`, `storage` and `blob` are always used as default and combined with the user input. You can provide additional tags to group or filter the health checks. + +### Variant 1: Configuration based +The first one is to use the configuration based approach. Therefor you have to add the configuration section `HealthChecks:AzureTableClient` to your `appsettings.json` file. +```csharp +var builder = services.AddHealthChecks(); + +builder.AddAzureTableClint(""); +``` + +The configuration looks like this: +```json +{ + ..., // other configuration + "HealthChecks": { + "AzureTableClient": { + "": { + "ConnectionString": "", // required + "ContainerName": "", // required + ..., // other configuration + "Timeout": "" // optional, default is 100 milliseconds + } + } + } +} +``` + +### Variant 2: Options based +The second one is to use the options based approach. Therefor you have to create an instance of `AzureTableClientOptions` and provide the configuration. +```csharp +var builder = services.AddHealthChecks(); + +builder.AddAzureTableClient("", options => +{ + options.ConnectionString = ""; + options.ContainerName = ""; + ... + options.Timeout = ""; +}); +``` + +### :bulb: You can always provide tags to all health checks, for grouping or filtering. + +```csharp +var builder = services.AddHealthChecks(); + builder.AddKafka("", options => ..., "azure"); +``` \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableConfigure.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableConfigure.cs new file mode 100644 index 00000000..bb5bc038 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableConfigure.cs @@ -0,0 +1,180 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using System.Threading; +using global::Azure.Data.Tables; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NetEvolve.Arguments; +using static Microsoft.Extensions.Options.ValidateOptionsResult; + +internal sealed class TableClientAvailableConfigure + : IConfigureNamedOptions, + IValidateOptions +{ + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + + public TableClientAvailableConfigure( + IConfiguration configuration, + IServiceProvider serviceProvider + ) + { + _configuration = configuration; + _serviceProvider = serviceProvider; + } + + public void Configure(string? name, TableClientAvailableOptions options) + { + Argument.ThrowIfNullOrWhiteSpace(name); + _configuration.Bind($"HealthChecks:AzureTableClient:{name}", options); + } + + public void Configure(TableClientAvailableOptions options) => + Configure(Options.DefaultName, options); + + public ValidateOptionsResult Validate(string? name, TableClientAvailableOptions options) + { + if (string.IsNullOrWhiteSpace(name)) + { + return Fail("The name cannot be null or whitespace."); + } + + if (options is null) + { + return Fail("The option cannot be null."); + } + + if (options.Timeout < Timeout.Infinite) + { + return Fail("The timeout cannot be less than infinite (-1)."); + } + + if (string.IsNullOrWhiteSpace(options.TableName)) + { + return Fail("The container name cannot be null or whitespace."); + } + + var mode = options.Mode; + + return options.Mode switch + { + TableClientCreationMode.ServiceProvider => ValidateModeServiceProvider(), + TableClientCreationMode.ConnectionString => ValidateModeConnectionString(options), + TableClientCreationMode.DefaultAzureCredentials + => ValidateModeDefaultAzureCredentials(options), + TableClientCreationMode.SharedKey => ValidateModeSharedKey(options), + TableClientCreationMode.AzureSasCredential => ValidateModeAzureSasCredential(options), + _ => Fail($"The mode `{mode}` is not supported."), + }; + } + + private static ValidateOptionsResult ValidateModeAzureSasCredential( + TableClientAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(TableClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(TableClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.ServiceUri.Query)) + { + return Fail( + $"The sas query token cannot be null or whitespace when using `{nameof(TableClientCreationMode.AzureSasCredential)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeSharedKey(TableClientAvailableOptions options) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountName)) + { + return Fail( + $"The account name cannot be null or whitespace when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountKey)) + { + return Fail( + $"The account key cannot be null or whitespace when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeDefaultAzureCredentials( + TableClientAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(TableClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(TableClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeConnectionString( + TableClientAvailableOptions options + ) + { + if (string.IsNullOrWhiteSpace(options.ConnectionString)) + { + return Fail( + $"The connection string cannot be null or whitespace when using `{nameof(TableClientCreationMode.ConnectionString)}` mode." + ); + } + + return Success; + } + + private ValidateOptionsResult ValidateModeServiceProvider() + { + if (_serviceProvider.GetService() is null) + { + return Fail( + $"No service of type `{nameof(TableServiceClient)}` registered. Please execute `builder.AddAzureClients()`." + ); + } + + return Success; + } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableHealthCheck.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableHealthCheck.cs new file mode 100644 index 00000000..cf18f383 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableHealthCheck.cs @@ -0,0 +1,51 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using System.Threading; +using System.Threading.Tasks; +using global::Azure.Data.Tables; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using NetEvolve.Extensions.Tasks; +using NetEvolve.HealthChecks.Abstractions; + +internal sealed class TableClientAvailableHealthCheck + : ConfigurableHealthCheckBase +{ + private readonly IServiceProvider _serviceProvider; + + public TableClientAvailableHealthCheck( + IServiceProvider serviceProvider, + IOptionsMonitor optionsMonitor + ) + : base(optionsMonitor) => _serviceProvider = serviceProvider; + + protected override async ValueTask ExecuteHealthCheckAsync( + string name, + HealthStatus failureStatus, + TableClientAvailableOptions options, + CancellationToken cancellationToken + ) + { + var tableClient = ClientCreation.GetTableServiceClient(name, options, _serviceProvider); + + var (isValid, result) = await tableClient + .QueryAsync(cancellationToken: cancellationToken) + .GetAsyncEnumerator(cancellationToken) + .MoveNextAsync() + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + (var tableInTime, _) = await tableClient + .GetTableClient(options.TableName) + .QueryAsync(cancellationToken: cancellationToken) + .GetAsyncEnumerator(cancellationToken) + .MoveNextAsync() + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + return isValid && result && tableInTime + ? HealthCheckResult.Healthy($"{name}: Healthy") + : HealthCheckResult.Degraded($"{name}: Degraded"); + } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableOptions.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableOptions.cs new file mode 100644 index 00000000..c648d846 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientAvailableOptions.cs @@ -0,0 +1,50 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using global::Azure.Data.Tables; + +/// +/// Options for the . +/// +public sealed class TableClientAvailableOptions : ITableOptions +{ + /// + /// Gets or sets the connection string. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the mode to create the client. + /// + public TableClientCreationMode Mode { get; set; } + + /// + /// The timeout to use when connecting and executing tasks against database. + /// + public int Timeout { get; set; } = 100; + + /// + /// Gets or sets the name of the table. + /// + public string? TableName { get; set; } + + /// + /// Gets or sets the service uri. + /// + public Uri? ServiceUri { get; set; } + + /// + /// Gets or sets the account name. + /// + public string? AccountName { get; set; } + + /// + /// Gets or sets the account key. + /// + public string? AccountKey { get; set; } + + /// + /// Gets or sets the lambda to configure the . + /// + public Action? ConfigureClientOptions { get; set; } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientCreationMode.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientCreationMode.cs new file mode 100644 index 00000000..60e0bcee --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableClientCreationMode.cs @@ -0,0 +1,37 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using global::Azure.Data.Tables; +using global::Azure.Identity; + +/// +/// Describes the mode used to create the . +/// +public enum TableClientCreationMode +{ + /// + /// The default mode. The is loading the preregistered instance from the . + /// + ServiceProvider = 0, + + /// + /// The is created using the . + /// + DefaultAzureCredentials = 1, + + /// + /// The is created using the . + /// + ConnectionString = 2, + + /// + /// The is created using the + /// and . As well as the . + /// + SharedKey = 3, + + /// + /// The is created using the . + /// + AzureSasCredential = 4 +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableConfigure.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableConfigure.cs new file mode 100644 index 00000000..be74639f --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableConfigure.cs @@ -0,0 +1,175 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using System.Threading; +using global::Azure.Data.Tables; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using NetEvolve.Arguments; +using static Microsoft.Extensions.Options.ValidateOptionsResult; + +internal sealed class TableServiceAvailableConfigure + : IConfigureNamedOptions, + IValidateOptions +{ + private readonly IConfiguration _configuration; + private readonly IServiceProvider _serviceProvider; + + public TableServiceAvailableConfigure( + IConfiguration configuration, + IServiceProvider serviceProvider + ) + { + _configuration = configuration; + _serviceProvider = serviceProvider; + } + + public void Configure(string? name, TableServiceAvailableOptions options) + { + Argument.ThrowIfNullOrWhiteSpace(name); + _configuration.Bind($"HealthChecks:AzureTableService:{name}", options); + } + + public void Configure(TableServiceAvailableOptions options) => + Configure(Options.DefaultName, options); + + public ValidateOptionsResult Validate(string? name, TableServiceAvailableOptions options) + { + if (string.IsNullOrWhiteSpace(name)) + { + return Fail("The name cannot be null or whitespace."); + } + + if (options is null) + { + return Fail("The option cannot be null."); + } + + if (options.Timeout < Timeout.Infinite) + { + return Fail("The timeout cannot be less than infinite (-1)."); + } + + var mode = options.Mode; + + return options.Mode switch + { + TableClientCreationMode.ServiceProvider => ValidateModeServiceProvider(), + TableClientCreationMode.ConnectionString => ValidateModeConnectionString(options), + TableClientCreationMode.DefaultAzureCredentials + => ValidateModeDefaultAzureCredentials(options), + TableClientCreationMode.SharedKey => ValidateModeSharedKey(options), + TableClientCreationMode.AzureSasCredential => ValidateModeAzureSasCredential(options), + _ => Fail($"The mode `{mode}` is not supported."), + }; + } + + private static ValidateOptionsResult ValidateModeAzureSasCredential( + TableServiceAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(TableClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(TableClientCreationMode.AzureSasCredential)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.ServiceUri.Query)) + { + return Fail( + $"The sas query token cannot be null or whitespace when using `{nameof(TableClientCreationMode.AzureSasCredential)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeSharedKey(TableServiceAvailableOptions options) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountName)) + { + return Fail( + $"The account name cannot be null or whitespace when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + if (string.IsNullOrWhiteSpace(options.AccountKey)) + { + return Fail( + $"The account key cannot be null or whitespace when using `{nameof(TableClientCreationMode.SharedKey)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeDefaultAzureCredentials( + TableServiceAvailableOptions options + ) + { + if (options.ServiceUri is null) + { + return Fail( + $"The service url cannot be null when using `{nameof(TableClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + if (!options.ServiceUri.IsAbsoluteUri) + { + return Fail( + $"The service url must be an absolute url when using `{nameof(TableClientCreationMode.DefaultAzureCredentials)}` mode." + ); + } + + return Success; + } + + private static ValidateOptionsResult ValidateModeConnectionString( + TableServiceAvailableOptions options + ) + { + if (string.IsNullOrWhiteSpace(options.ConnectionString)) + { + return Fail( + $"The connection string cannot be null or whitespace when using `{nameof(TableClientCreationMode.ConnectionString)}` mode." + ); + } + + return Success; + } + + private ValidateOptionsResult ValidateModeServiceProvider() + { + if (_serviceProvider.GetService() is null) + { + return Fail( + $"No service of type `{nameof(TableServiceClient)}` registered. Please execute `builder.AddAzureClients()`." + ); + } + + return Success; + } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableHealthCheck.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableHealthCheck.cs new file mode 100644 index 00000000..e1da760d --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableHealthCheck.cs @@ -0,0 +1,42 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Options; +using NetEvolve.Extensions.Tasks; +using NetEvolve.HealthChecks.Abstractions; + +internal sealed class TableServiceAvailableHealthCheck + : ConfigurableHealthCheckBase +{ + private readonly IServiceProvider _serviceProvider; + + public TableServiceAvailableHealthCheck( + IServiceProvider serviceProvider, + IOptionsMonitor optionsMonitor + ) + : base(optionsMonitor) => _serviceProvider = serviceProvider; + + protected override async ValueTask ExecuteHealthCheckAsync( + string name, + HealthStatus failureStatus, + TableServiceAvailableOptions options, + CancellationToken cancellationToken + ) + { + var tableClient = ClientCreation.GetTableServiceClient(name, options, _serviceProvider); + + var (isValid, result) = await tableClient + .QueryAsync(cancellationToken: cancellationToken) + .GetAsyncEnumerator(cancellationToken) + .MoveNextAsync() + .WithTimeoutAsync(options.Timeout, cancellationToken) + .ConfigureAwait(false); + + return isValid && result + ? HealthCheckResult.Healthy($"{name}: Healthy") + : HealthCheckResult.Degraded($"{name}: Degraded"); + } +} diff --git a/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableOptions.cs b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableOptions.cs new file mode 100644 index 00000000..944ffedf --- /dev/null +++ b/src/HealthChecks.Azure.Tables/src/NetEvolve.HealthChecks.Azure.Tables/TableServiceAvailableOptions.cs @@ -0,0 +1,45 @@ +namespace NetEvolve.HealthChecks.Azure.Tables; + +using System; +using global::Azure.Data.Tables; + +/// +/// Options for the . +/// +public sealed class TableServiceAvailableOptions : ITableOptions +{ + /// + /// Gets or sets the connection string. + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the mode to create the client. + /// + public TableClientCreationMode Mode { get; set; } + + /// + /// Gets or sets the timeout in milliseconds for executing the healthcheck. + /// + public int Timeout { get; set; } = 100; + + /// + /// Gets or sets the service uri. + /// + public Uri? ServiceUri { get; set; } + + /// + /// Gets or sets the account name. + /// + public string? AccountName { get; set; } + + /// + /// Gets or sets the account key. + /// + public string? AccountKey { get; set; } + + /// + /// Gets or sets the lambda to configure the . + /// + public Action? ConfigureClientOptions { get; set; } +} diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/AzuriteAccess.cs b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/AzuriteAccess.cs new file mode 100644 index 00000000..a3fa71fe --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/AzuriteAccess.cs @@ -0,0 +1,28 @@ +namespace NetEvolve.HealthChecks.Azure.Tables.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Testcontainers.Azurite; +using Xunit; + +[ExcludeFromCodeCoverage] +public sealed class AzuriteAccess : IAsyncLifetime, IAsyncDisposable +{ + public const string AccountName = "devstoreaccount1"; + public const string AccountKey = + "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="; + + private readonly AzuriteContainer _container = new AzuriteBuilder() + .WithImage("mcr.microsoft.com/azure-storage/azurite:latest") + .Build(); + + public string ConnectionString => _container.GetConnectionString(); + + public async Task DisposeAsync() => await _container.DisposeAsync().ConfigureAwait(false); + + public async Task InitializeAsync() => await _container.StartAsync().ConfigureAwait(false); + + async ValueTask IAsyncDisposable.DisposeAsync() => + await _container.DisposeAsync().ConfigureAwait(false); +} diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration.csproj b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration.csproj new file mode 100644 index 00000000..922864a3 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration.csproj @@ -0,0 +1,33 @@ + + + + $(_TestTargetFrameworks) + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/TableClientAvailableHealthCheckTests.cs b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/TableClientAvailableHealthCheckTests.cs new file mode 100644 index 00000000..a1ed08aa --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/TableClientAvailableHealthCheckTests.cs @@ -0,0 +1,262 @@ +namespace NetEvolve.HealthChecks.Azure.Tables.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading.Tasks; +using Argon; +using global::Azure.Data.Tables; +using global::Azure.Data.Tables.Sas; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Tests; +using Xunit; + +[IntegrationTest] +[ExcludeFromCodeCoverage] +[SetCulture("en-US")] +public class TableClientAvailableHealthCheckTests + : HealthCheckTestBase, + IClassFixture +{ + private readonly AzuriteAccess _container; + private readonly Uri _accountSasUri; + private readonly Uri _uriTableStorage; + + public TableClientAvailableHealthCheckTests(AzuriteAccess container) + { + _container = container; + + var client = new TableServiceClient(_container.ConnectionString); + _uriTableStorage = client.Uri; + + var tableClient = client.GetTableClient("test"); + _ = tableClient.CreateIfNotExists(); + + _accountSasUri = tableClient.GenerateSasUri( + TableSasPermissions.All, + DateTimeOffset.UtcNow.AddDays(1) + ); + } + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableServiceProviderHealthy", + options => + { + options.TableName = "test"; + options.Mode = TableClientCreationMode.ServiceProvider; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddTableServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableServiceProviderDegraded", + options => + { + options.TableName = "test"; + options.Mode = TableClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddTableServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableServiceProviderNotExistsUnhealthy", + options => + { + options.TableName = "notexists"; + options.Mode = TableClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddTableServiceClient(_container.ConnectionString) + ); + }, + clearJToken: token => + { + if (token is null) + { + return null; + } + + if ( + token.Value("status") is string status + && status.Equals( + nameof(HealthCheckResult.Unhealthy), + StringComparison.OrdinalIgnoreCase + ) + ) + { + var results = token["results"].FirstOrDefault(); + + if (results?["exception"] is not null) + { + results["exception"]!["message"] = null; + } + } + + return token; + } + ); + + [Fact] + public async Task AddTableClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableServiceProviderWithConfigurationHealthy", + options => + { + options.TableName = "test"; + options.Mode = TableClientCreationMode.ServiceProvider; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddTableServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableConnectionStringHealthy", + options => + { + options.TableName = "test"; + options.ConnectionString = _container.ConnectionString; + options.Mode = TableClientCreationMode.ConnectionString; + } + ); + }); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableConnectionStringDegraded", + options => + { + options.TableName = "test"; + options.ConnectionString = _container.ConnectionString; + options.Mode = TableClientCreationMode.ConnectionString; + options.Timeout = 0; + } + ); + }); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableSharedKeyHealthy", + options => + { + options.TableName = "test"; + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = TableClientCreationMode.SharedKey; + options.ServiceUri = _uriTableStorage; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableSharedKeyDegraded", + options => + { + options.TableName = "test"; + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = TableClientCreationMode.SharedKey; + options.Timeout = 0; + options.ServiceUri = _uriTableStorage; + } + ); + }); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableAzureSasCredentialHealthy", + options => + { + options.TableName = "test"; + options.Mode = TableClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + } + ); + }); + + [Fact] + public async Task AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableClientAvailability( + "TableAzureSasCredentialDegraded", + options => + { + options.TableName = "test"; + options.Mode = TableClientCreationMode.AzureSasCredential; + options.ServiceUri = _accountSasUri; + options.Timeout = 0; + } + ); + }); +} diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/TableServiceAvailableHealthCheckTests.cs b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/TableServiceAvailableHealthCheckTests.cs new file mode 100644 index 00000000..5aa01bda --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/TableServiceAvailableHealthCheckTests.cs @@ -0,0 +1,164 @@ +namespace NetEvolve.HealthChecks.Azure.Tables.Tests.Integration; + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using global::Azure.Data.Tables; +using global::Azure.Data.Tables.Sas; +using Microsoft.Extensions.Azure; +using NetEvolve.Extensions.XUnit; +using NetEvolve.HealthChecks.Tests; +using Xunit; + +[IntegrationTest] +[ExcludeFromCodeCoverage] +[SetCulture("en-US")] +public class TableServiceAvailableHealthCheckTests + : HealthCheckTestBase, + IClassFixture +{ + private readonly AzuriteAccess _container; + private readonly Uri _uriTableStorage; + + public TableServiceAvailableHealthCheckTests(AzuriteAccess container) + { + _container = container; + + var client = new TableServiceClient(_container.ConnectionString); + _uriTableStorage = client.Uri; + } + + [Fact] + public async Task AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddTableServiceAvailability( + "ServiceServiceProviderHealthy", + options => + { + options.Mode = TableClientCreationMode.ServiceProvider; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddTableServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddTableServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddTableServiceAvailability( + "ServiceServiceProviderWithConfigurationHealthy", + options => + { + options.Mode = TableClientCreationMode.ServiceProvider; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddTableServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded() => + await RunAndVerify( + healthChecks => + { + _ = healthChecks.AddTableServiceAvailability( + "ServiceServiceProviderDegraded", + options => + { + options.Mode = TableClientCreationMode.ServiceProvider; + options.Timeout = 0; + } + ); + }, + serviceBuilder: services => + { + services.AddAzureClients(clients => + _ = clients.AddTableServiceClient(_container.ConnectionString) + ); + } + ); + + [Fact] + public async Task AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableServiceAvailability( + "ServiceConnectionStringHealthy", + options => + { + options.ConnectionString = _container.ConnectionString; + options.Mode = TableClientCreationMode.ConnectionString; + } + ); + }); + + [Fact] + public async Task AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableServiceAvailability( + "ServiceConnectionStringDegraded", + options => + { + options.ConnectionString = _container.ConnectionString; + options.Mode = TableClientCreationMode.ConnectionString; + options.Timeout = 0; + } + ); + }); + + [Fact] + public async Task AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableServiceAvailability( + "ServiceSharedKeyHealthy", + options => + { + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = TableClientCreationMode.SharedKey; + options.ServiceUri = _uriTableStorage; + options.ConfigureClientOptions = clientOptions => + { + clientOptions.Retry.MaxRetries = 0; + }; + } + ); + }); + + [Fact] + public async Task AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded() => + await RunAndVerify(healthChecks => + { + _ = healthChecks.AddTableServiceAvailability( + "ServiceSharedKeyDegraded", + options => + { + options.AccountKey = AzuriteAccess.AccountKey; + options.AccountName = AzuriteAccess.AccountName; + options.Mode = TableClientCreationMode.SharedKey; + options.Timeout = 0; + options.ServiceUri = _uriTableStorage; + } + ); + }); +} diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..7d20901c --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableServiceProviderWithConfigurationHealthy: Healthy, + name: TableServiceProviderWithConfigurationHealthy, + status: Healthy, + tags: [ + storage, + azure, + table + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..747f78ad --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableAzureSasCredentialDegraded: Degraded, + name: TableAzureSasCredentialDegraded, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..5a157543 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeAzureSasCredential_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableAzureSasCredentialHealthy: Degraded, + name: TableAzureSasCredentialHealthy, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..8e7bb037 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableConnectionStringDegraded: Degraded, + name: TableConnectionStringDegraded, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..43536fc8 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableConnectionStringHealthy: Healthy, + name: TableConnectionStringHealthy, + status: Healthy, + tags: [ + storage, + azure, + table + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..d8177fce --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableServiceProviderDegraded: Degraded, + name: TableServiceProviderDegraded, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..ddf64b60 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableServiceProviderHealthy: Healthy, + name: TableServiceProviderHealthy, + status: Healthy, + tags: [ + storage, + azure, + table + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt new file mode 100644 index 00000000..a0bb1d01 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeServiceProvider_ShouldReturnUnhealthy.verified.txt @@ -0,0 +1,19 @@ +{ + results: [ + { + description: TableServiceProviderNotExistsUnhealthy: Unexpected error., + exception: { + message: null, + type: Azure.RequestFailedException + }, + name: TableServiceProviderNotExistsUnhealthy, + status: Unhealthy, + tags: [ + storage, + azure, + table + ] + } + ], + status: Unhealthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..6935acac --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableSharedKeyDegraded: Degraded, + name: TableSharedKeyDegraded, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..a8cbf3b0 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableClientAvailableHealthCheckTests.AddTableClientAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: TableSharedKeyHealthy: Healthy, + name: TableSharedKeyHealthy, + status: Healthy, + tags: [ + storage, + azure, + table + ] + } + ], + status: Healthy +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..27cd0f7c --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptionsWithAdditionalConfiguration_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderWithConfigurationHealthy: Degraded, + name: ServiceServiceProviderWithConfigurationHealthy, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..8da0bd25 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceConnectionStringDegraded: Degraded, + name: ServiceConnectionStringDegraded, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..17f15a4a --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeConnectionString_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceConnectionStringHealthy: Degraded, + name: ServiceConnectionStringHealthy, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..e37f9c62 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderDegraded: Degraded, + name: ServiceServiceProviderDegraded, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..485bb76a --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeServiceProvider_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceServiceProviderHealthy: Degraded, + name: ServiceServiceProviderHealthy, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt new file mode 100644 index 00000000..bca9a5bd --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnDegraded.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceSharedKeyDegraded: Degraded, + name: ServiceSharedKeyDegraded, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt new file mode 100644 index 00000000..9519c43b --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Integration/_snapshot/TableServiceAvailableHealthCheckTests.AddTableServiceAvailability_UseOptions_ModeSharedKey_ShouldReturnHealthy.verified.txt @@ -0,0 +1,15 @@ +{ + results: [ + { + description: ServiceSharedKeyHealthy: Degraded, + name: ServiceSharedKeyHealthy, + status: Degraded, + tags: [ + storage, + azure, + table + ] + } + ], + status: Degraded +} \ No newline at end of file diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/ClientCreationTests.cs b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/ClientCreationTests.cs new file mode 100644 index 00000000..9ab4bf36 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/ClientCreationTests.cs @@ -0,0 +1,23 @@ +namespace NetEvolve.HealthChecks.Azure.Tables.Tests.Unit; + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public class ClientCreationTests +{ + [Fact] + public void CreateTableServiceClient_InvalidMode_ThrowUnreachableException() + { + var options = new TableClientAvailableOptions { Mode = (TableClientCreationMode)13 }; + var serviceProvider = new ServiceCollection().BuildServiceProvider(); + + _ = Assert.Throws( + () => ClientCreation.CreateTableServiceClient(options, serviceProvider) + ); + } +} diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/DependencyInjectionExtensionsTests.cs b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/DependencyInjectionExtensionsTests.cs new file mode 100644 index 00000000..c1356f8c --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/DependencyInjectionExtensionsTests.cs @@ -0,0 +1,171 @@ +namespace NetEvolve.HealthChecks.Azure.Tables.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public class DependencyInjectionExtensionsTests +{ + [Fact] + public void AddTableClientAvailability_WhenArgumentBuilderNull_ThrowArgumentNullException() + { + // Arrange + var builder = default(IHealthChecksBuilder); + + // Act + void Act() => _ = builder.AddTableClientAvailability("Test"); + + // Assert + _ = Assert.Throws("builder", Act); + } + + [Fact] + public void AddTableClientAvailability_WhenArgumentNameNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = default(string); + + // Act + void Act() => _ = builder.AddTableClientAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddTableClientAvailability_WhenArgumentNameEmpty_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = string.Empty; + + // Act + void Act() => _ = builder.AddTableClientAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddTableClientAvailability_WhenArgumentTagsNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var tags = default(string[]); + + // Act + void Act() => _ = builder.AddTableClientAvailability("Test", tags: tags); + + // Assert + _ = Assert.Throws("tags", Act); + } + + [Fact] + public void AddTableClientAvailability_WhenArgumentNameIsAlreadyUsed_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + const string name = "Test"; + + // Act + void Act() => + _ = builder.AddTableClientAvailability(name, x => { }).AddTableClientAvailability(name); + + // Assert + _ = Assert.Throws(nameof(name), Act); + } + + [Fact] + public void AddTableServiceAvailability_WhenArgumentBuilderNull_ThrowArgumentNullException() + { + // Arrange + var builder = default(IHealthChecksBuilder); + + // Act + void Act() => _ = builder.AddTableServiceAvailability("Test"); + + // Assert + _ = Assert.Throws("builder", Act); + } + + [Fact] + public void AddTableServiceAvailability_WhenArgumentNameNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = default(string); + + // Act + void Act() => _ = builder.AddTableServiceAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddTableServiceAvailability_WhenArgumentNameEmpty_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var name = string.Empty; + + // Act + void Act() => _ = builder.AddTableServiceAvailability(name); + + // Assert + _ = Assert.Throws("name", Act); + } + + [Fact] + public void AddTableServiceAvailability_WhenArgumentTagsNull_ThrowArgumentNullException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + var tags = default(string[]); + + // Act + void Act() => _ = builder.AddTableServiceAvailability("Test", tags: tags); + + // Assert + _ = Assert.Throws("tags", Act); + } + + [Fact] + public void AddTableServiceAvailability_WhenArgumentNameIsAlreadyUsed_ThrowArgumentException() + { + // Arrange + var configuration = new ConfigurationBuilder().Build(); + var services = new ServiceCollection(); + var builder = services.AddSingleton(configuration).AddHealthChecks(); + const string name = "Test"; + + // Act + void Act() => + _ = builder + .AddTableServiceAvailability(name, x => { }) + .AddTableServiceAvailability(name); + + // Assert + _ = Assert.Throws(nameof(name), Act); + } +} diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit.csproj b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit.csproj new file mode 100644 index 00000000..03e572e3 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit.csproj @@ -0,0 +1,27 @@ + + + + $(_TestTargetFrameworks) + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/TableClientAvailableConfigureTests.cs b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/TableClientAvailableConfigureTests.cs new file mode 100644 index 00000000..8229a982 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/TableClientAvailableConfigureTests.cs @@ -0,0 +1,262 @@ +namespace NetEvolve.HealthChecks.Azure.Tables.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using global::Azure.Data.Tables; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public sealed class TableClientAvailableConfigureTests +{ + [Fact] + public void Configue_OnlyOptions_ThrowsArgumentException() + { + // Arrange + var services = new ServiceCollection(); + var configure = new TableClientAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + var options = new TableClientAvailableOptions(); + + // Act / Assert + _ = Assert.Throws("name", () => configure.Configure(options)); + } + + [Theory] + [MemberData(nameof(GetValidateTestCases))] + public void Validate_Theory_Expected( + bool expectedResult, + string? expectedMessage, + string? name, + TableClientAvailableOptions options + ) + { + // Arrange + var services = new ServiceCollection(); + var configure = new TableClientAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + + // Act + var result = configure.Validate(name, options); + + // Assert + Assert.Equal(expectedResult, result.Succeeded); + Assert.Equal(expectedMessage, result.FailureMessage); + } + + public static TheoryData< + bool, + string?, + string?, + TableClientAvailableOptions + > GetValidateTestCases() + { + var data = new TheoryData + { + { false, "The name cannot be null or whitespace.", null, null! }, + { false, "The name cannot be null or whitespace.", "\t", null! }, + { false, "The option cannot be null.", "name", null! }, + { + false, + "The timeout cannot be less than infinite (-1).", + "name", + new TableClientAvailableOptions { Timeout = -2 } + }, + { + false, + "The container name cannot be null or whitespace.", + "name", + new TableClientAvailableOptions() + }, + { + false, + "The mode `13` is not supported.", + "name", + new TableClientAvailableOptions + { + Mode = (TableClientCreationMode)13, + TableName = "test" + } + }, + // Mode: ServiceProvider + { + false, + $"No service of type `{nameof(TableServiceClient)}` registered. Please execute `builder.AddAzureClients()`.", + "name", + new TableClientAvailableOptions + { + Mode = TableClientCreationMode.ServiceProvider, + TableName = "test" + } + }, + // Mode: DefaultAzureCredentials + { + false, + "The service url cannot be null when using `DefaultAzureCredentials` mode.", + "name", + new TableClientAvailableOptions + { + Mode = TableClientCreationMode.DefaultAzureCredentials, + TableName = "test" + } + }, + { + false, + "The service url must be an absolute url when using `DefaultAzureCredentials` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + true, + null, + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("https://example.com", UriKind.Absolute) + } + }, + // Mode: ConnectionString + { + false, + "The connection string cannot be null or whitespace when using `ConnectionString` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.ConnectionString + } + }, + { + true, + null, + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.ConnectionString, + ConnectionString = "connectionString" + } + }, + // Mode: SharedKey + { + false, + "The service url cannot be null when using `SharedKey` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.SharedKey + } + }, + { + false, + "The service url must be an absolute url when using `SharedKey` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The account name cannot be null or whitespace when using `SharedKey` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = null + } + }, + { + false, + "The account key cannot be null or whitespace when using `SharedKey` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = null + } + }, + { + true, + null, + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = "test" + } + }, + // Mode: AzureSasCredential + { + false, + "The service url cannot be null when using `AzureSasCredential` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.AzureSasCredential + } + }, + { + false, + "The service url must be an absolute url when using `AzureSasCredential` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The sas query token cannot be null or whitespace when using `AzureSasCredential` mode.", + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute", UriKind.Absolute) + } + }, + { + true, + null, + "name", + new TableClientAvailableOptions + { + TableName = "test", + Mode = TableClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute?query=test", UriKind.Absolute) + } + } + }; + + return data; + } +} diff --git a/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/TableServiceAvailableConfigureTests.cs b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/TableServiceAvailableConfigureTests.cs new file mode 100644 index 00000000..6ab28691 --- /dev/null +++ b/src/HealthChecks.Azure.Tables/tests/NetEvolve.HealthChecks.Azure.Tables.Tests.Unit/TableServiceAvailableConfigureTests.cs @@ -0,0 +1,228 @@ +namespace NetEvolve.HealthChecks.Azure.Tables.Tests.Unit; + +using System; +using System.Diagnostics.CodeAnalysis; +using global::Azure.Data.Tables; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using NetEvolve.Extensions.XUnit; +using Xunit; + +[ExcludeFromCodeCoverage] +[UnitTest] +public sealed class TableServiceAvailableConfigureTests +{ + [Fact] + public void Configue_OnlyOptions_ThrowsArgumentException() + { + // Arrange + var services = new ServiceCollection(); + var configure = new TableServiceAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + var options = new TableServiceAvailableOptions(); + + // Act / Assert + _ = Assert.Throws("name", () => configure.Configure(options)); + } + + [Theory] + [MemberData(nameof(GetValidateTestCases))] + public void Validate_Theory_Expected( + bool expectedResult, + string? expectedMessage, + string? name, + TableServiceAvailableOptions options + ) + { + // Arrange + var services = new ServiceCollection(); + var configure = new TableServiceAvailableConfigure( + new ConfigurationBuilder().Build(), + services.BuildServiceProvider() + ); + + // Act + var result = configure.Validate(name, options); + + // Assert + Assert.Equal(expectedResult, result.Succeeded); + Assert.Equal(expectedMessage, result.FailureMessage); + } + + public static TheoryData< + bool, + string?, + string?, + TableServiceAvailableOptions + > GetValidateTestCases() + { + var data = new TheoryData + { + { false, "The name cannot be null or whitespace.", null, null! }, + { false, "The name cannot be null or whitespace.", "\t", null! }, + { false, "The option cannot be null.", "name", null! }, + { + false, + "The timeout cannot be less than infinite (-1).", + "name", + new TableServiceAvailableOptions { Timeout = -2 } + }, + { + false, + "The mode `13` is not supported.", + "name", + new TableServiceAvailableOptions { Mode = (TableClientCreationMode)13 } + }, + // Mode: ServiceProvider + { + false, + $"No service of type `{nameof(TableServiceClient)}` registered. Please execute `builder.AddAzureClients()`.", + "name", + new TableServiceAvailableOptions { Mode = TableClientCreationMode.ServiceProvider } + }, + // Mode: DefaultAzureCredentials + { + false, + "The service url cannot be null when using `DefaultAzureCredentials` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.DefaultAzureCredentials + } + }, + { + false, + "The service url must be an absolute url when using `DefaultAzureCredentials` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + true, + null, + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.DefaultAzureCredentials, + ServiceUri = new Uri("https://example.com", UriKind.Absolute) + } + }, + // Mode: ConnectionString + { + false, + "The connection string cannot be null or whitespace when using `ConnectionString` mode.", + "name", + new TableServiceAvailableOptions { Mode = TableClientCreationMode.ConnectionString } + }, + { + true, + null, + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.ConnectionString, + ConnectionString = "connectionString" + } + }, + // Mode: SharedKey + { + false, + "The service url cannot be null when using `SharedKey` mode.", + "name", + new TableServiceAvailableOptions { Mode = TableClientCreationMode.SharedKey } + }, + { + false, + "The service url must be an absolute url when using `SharedKey` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The account name cannot be null or whitespace when using `SharedKey` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = null + } + }, + { + false, + "The account key cannot be null or whitespace when using `SharedKey` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = null + } + }, + { + true, + null, + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.SharedKey, + ServiceUri = new Uri("https://example.com", UriKind.Absolute), + AccountName = "test", + AccountKey = "test" + } + }, + // Mode: AzureSasCredential + { + false, + "The service url cannot be null when using `AzureSasCredential` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.AzureSasCredential + } + }, + { + false, + "The service url must be an absolute url when using `AzureSasCredential` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("/relative", UriKind.Relative) + } + }, + { + false, + "The sas query token cannot be null or whitespace when using `AzureSasCredential` mode.", + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute", UriKind.Absolute) + } + }, + { + true, + null, + "name", + new TableServiceAvailableOptions + { + Mode = TableClientCreationMode.AzureSasCredential, + ServiceUri = new Uri("https://absolute?query=test", UriKind.Absolute) + } + } + }; + + return data; + } +} diff --git a/src/HealthChecks.ClickHouse/src/NetEvolve.HealthChecks.ClickHouse/DependencyInjectionExtensions.cs b/src/HealthChecks.ClickHouse/src/NetEvolve.HealthChecks.ClickHouse/DependencyInjectionExtensions.cs index 1191eb2b..503a64b4 100644 --- a/src/HealthChecks.ClickHouse/src/NetEvolve.HealthChecks.ClickHouse/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.ClickHouse/src/NetEvolve.HealthChecks.ClickHouse/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.Dapr/src/NetEvolve.HealthChecks.Dapr/DependencyInjectionExtensions.cs b/src/HealthChecks.Dapr/src/NetEvolve.HealthChecks.Dapr/DependencyInjectionExtensions.cs index b36c80b7..8e5d9097 100644 --- a/src/HealthChecks.Dapr/src/NetEvolve.HealthChecks.Dapr/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.Dapr/src/NetEvolve.HealthChecks.Dapr/DependencyInjectionExtensions.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.MySql.Connector/src/NetEvolve.HealthChecks.MySql.Connector/DependencyInjectionExtensions.cs b/src/HealthChecks.MySql.Connector/src/NetEvolve.HealthChecks.MySql.Connector/DependencyInjectionExtensions.cs index d3dcc951..3fb6e409 100644 --- a/src/HealthChecks.MySql.Connector/src/NetEvolve.HealthChecks.MySql.Connector/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.MySql.Connector/src/NetEvolve.HealthChecks.MySql.Connector/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.MySql/src/NetEvolve.HealthChecks.MySql/DependencyInjectionExtensions.cs b/src/HealthChecks.MySql/src/NetEvolve.HealthChecks.MySql/DependencyInjectionExtensions.cs index 9514ab1e..5636c6a8 100644 --- a/src/HealthChecks.MySql/src/NetEvolve.HealthChecks.MySql/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.MySql/src/NetEvolve.HealthChecks.MySql/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.Npgsql/src/NetEvolve.HealthChecks.Npgsql/DependencyInjectionExtensions.cs b/src/HealthChecks.Npgsql/src/NetEvolve.HealthChecks.Npgsql/DependencyInjectionExtensions.cs index 725180a1..9da85d77 100644 --- a/src/HealthChecks.Npgsql/src/NetEvolve.HealthChecks.Npgsql/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.Npgsql/src/NetEvolve.HealthChecks.Npgsql/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.Oracle/src/NetEvolve.HealthChecks.Oracle/DependencyInjectionExtensions.cs b/src/HealthChecks.Oracle/src/NetEvolve.HealthChecks.Oracle/DependencyInjectionExtensions.cs index 519fd55c..1c36e3a0 100644 --- a/src/HealthChecks.Oracle/src/NetEvolve.HealthChecks.Oracle/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.Oracle/src/NetEvolve.HealthChecks.Oracle/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.Redpanda/src/NetEvolve.HealthChecks.Redpanda/DependencyInjectionExtensions.cs b/src/HealthChecks.Redpanda/src/NetEvolve.HealthChecks.Redpanda/DependencyInjectionExtensions.cs index 58633711..f2b2bf55 100644 --- a/src/HealthChecks.Redpanda/src/NetEvolve.HealthChecks.Redpanda/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.Redpanda/src/NetEvolve.HealthChecks.Redpanda/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/DependencyInjectionExtensionsTests.cs b/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/DependencyInjectionExtensionsTests.cs index 78de26e0..ea167536 100644 --- a/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/DependencyInjectionExtensionsTests.cs +++ b/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/DependencyInjectionExtensionsTests.cs @@ -22,8 +22,7 @@ public void AddRedpanda_WhenArgumentBuilderNull_ThrowArgumentNullException() void Act() => _ = builder.AddRedpanda("Test"); // Assert - var ex = Assert.Throws("builder", Act); - Assert.Equal("Value cannot be null. (Parameter 'builder')", ex.Message); + _ = Assert.Throws("builder", Act); } [Fact] @@ -39,8 +38,7 @@ public void AddRedpanda_WhenArgumentNameNull_ThrowArgumentNullException() void Act() => _ = builder.AddRedpanda(name); // Assert - var ex = Assert.Throws("name", Act); - Assert.Equal("Value cannot be null. (Parameter 'name')", ex.Message); + _ = Assert.Throws("name", Act); } [Fact] diff --git a/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/RedpandaConfigureTests.cs b/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/RedpandaConfigureTests.cs index 114a6c35..814530c2 100644 --- a/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/RedpandaConfigureTests.cs +++ b/src/HealthChecks.Redpanda/tests/NetEvolve.HealthChecks.Redpanda.Tests.Unit/RedpandaConfigureTests.cs @@ -132,8 +132,7 @@ public void Configure_WhenArgumentNameNull_ThrowArgumentNullException() void Act() => configure.Configure(name, options); // Assert - var ex = Assert.Throws("name", Act); - Assert.Equal("Value cannot be null. (Parameter 'name')", ex.Message); + _ = Assert.Throws("name", Act); } [Fact] diff --git a/src/HealthChecks.SQLite/src/NetEvolve.HealthChecks.SQLite/DependencyInjectionExtensions.cs b/src/HealthChecks.SQLite/src/NetEvolve.HealthChecks.SQLite/DependencyInjectionExtensions.cs index 38c0fcbb..a6ca4a52 100644 --- a/src/HealthChecks.SQLite/src/NetEvolve.HealthChecks.SQLite/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.SQLite/src/NetEvolve.HealthChecks.SQLite/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/HealthCheckTestBase.cs b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/HealthCheckTestBase.cs index 9832bcb4..fa758e76 100644 --- a/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/HealthCheckTestBase.cs +++ b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/HealthCheckTestBase.cs @@ -28,7 +28,8 @@ protected async ValueTask RunAndVerify( Action healthChecks, Action? config = null, Action? serviceBuilder = null, - Action? serverConfiguration = null + Action? serverConfiguration = null, + Func? clearJToken = null ) { var builder = new WebHostBuilder() @@ -62,6 +63,11 @@ protected async ValueTask RunAndVerify( var resultContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var content = string.IsNullOrWhiteSpace(resultContent) ? null : Argon.JToken.Parse(resultContent); + if (clearJToken is not null) + { + content = clearJToken.Invoke(content); + } + _ = await Verifier .Verify(content) .UseDirectory(GetProjectDirectory()) diff --git a/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/Internals.cs b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/Internals.cs new file mode 100644 index 00000000..f1d5d77c --- /dev/null +++ b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/Internals.cs @@ -0,0 +1,3 @@ +using Xunit; + +[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)] diff --git a/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.projitems b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.projitems index 51506372..bb56500b 100644 --- a/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.projitems +++ b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.projitems @@ -11,6 +11,7 @@ + diff --git a/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.shproj b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.shproj index d9c88477..06aac2a1 100644 --- a/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.shproj +++ b/src/HealthChecks.Shared/tests/NetEvolve.HealthChecks.Shared.Tests/NetEvolve.HealthChecks.Shared.Tests.shproj @@ -10,6 +10,7 @@ + diff --git a/src/HealthChecks.SqlEdge/src/NetEvolve.HealthChecks.SqlEdge/DependencyInjectionExtensions.cs b/src/HealthChecks.SqlEdge/src/NetEvolve.HealthChecks.SqlEdge/DependencyInjectionExtensions.cs index d5f0bf8e..c852a3ac 100644 --- a/src/HealthChecks.SqlEdge/src/NetEvolve.HealthChecks.SqlEdge/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.SqlEdge/src/NetEvolve.HealthChecks.SqlEdge/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.SqlServer.Legacy/src/NetEvolve.HealthChecks.SqlServer.Legacy/DependencyInjectionExtensions.cs b/src/HealthChecks.SqlServer.Legacy/src/NetEvolve.HealthChecks.SqlServer.Legacy/DependencyInjectionExtensions.cs index 3e9e4faa..12c03434 100644 --- a/src/HealthChecks.SqlServer.Legacy/src/NetEvolve.HealthChecks.SqlServer.Legacy/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.SqlServer.Legacy/src/NetEvolve.HealthChecks.SqlServer.Legacy/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks.SqlServer/src/NetEvolve.HealthChecks.SqlServer/DependencyInjectionExtensions.cs b/src/HealthChecks.SqlServer/src/NetEvolve.HealthChecks.SqlServer/DependencyInjectionExtensions.cs index ba43aa6a..f22d3477 100644 --- a/src/HealthChecks.SqlServer/src/NetEvolve.HealthChecks.SqlServer/DependencyInjectionExtensions.cs +++ b/src/HealthChecks.SqlServer/src/NetEvolve.HealthChecks.SqlServer/DependencyInjectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; using NetEvolve.Arguments; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks. diff --git a/src/HealthChecks/src/NetEvolve.HealthChecks/DependencyInjectionExtensions.cs b/src/HealthChecks/src/NetEvolve.HealthChecks/DependencyInjectionExtensions.cs index a663b4fa..be48ff31 100644 --- a/src/HealthChecks/src/NetEvolve.HealthChecks/DependencyInjectionExtensions.cs +++ b/src/HealthChecks/src/NetEvolve.HealthChecks/DependencyInjectionExtensions.cs @@ -5,6 +5,7 @@ using System.Linq; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Diagnostics.HealthChecks; +using NetEvolve.HealthChecks.Abstractions; /// /// Extensions methods for with custom Health Checks.