From 98704e009a5b883b50a5fce5a522d15cb9755ea7 Mon Sep 17 00:00:00 2001 From: Brian Pursley Date: Thu, 19 Dec 2024 16:48:40 -0500 Subject: [PATCH] Added dotnet format step to build - Added default .editorconfig (dotnet new editorconfig) - Applied dotnet format changes - Added build step to verify no formatting changes are needed --- .editorconfig | 363 ++++++++++++++++++++++++++++++++++ .github/workflows/build.yml | 11 +- Npgmq.Example/Program.cs | 3 + Npgmq.Test/NpgmqClientTest.cs | 131 ++++++------ Npgmq.sln | 2 + Npgmq/INpgmqClient.cs | 18 +- Npgmq/NpgmqClient.cs | 22 ++- Npgmq/NpgmqCommand.cs | 5 +- Npgmq/NpgmqCommandFactory.cs | 5 +- Npgmq/NpgmqException.cs | 2 +- Npgmq/NpgmqMessage.cs | 6 +- Npgmq/NpgmqMetricsResult.cs | 10 +- Npgmq/NpgmqQueue.cs | 6 +- 13 files changed, 482 insertions(+), 102 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5ffa69c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,363 @@ +root = true + +# All files +[*] +indent_style = space + +# Xml files +[*.xml] +indent_size = 2 + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +tab_width = 4 + +# New line preferences +insert_final_newline = false + +#### .NET Coding Conventions #### +[*.{cs,vb}] + +# Organize usings +dotnet_separate_import_directive_groups = true +dotnet_sort_system_directives_first = true +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false:silent +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_property = false:silent + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# Expression-level preferences +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_object_initializer = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + +# Field preferences +dotnet_style_readonly_field = true:warning + +# Parameter preferences +dotnet_code_quality_unused_parameters = all:suggestion + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +#### C# Coding Conventions #### +[*.cs] + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:suggestion +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_prefer_switch_expression = true:suggestion + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_prefer_static_local_function = true:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = all +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### Naming styles #### +[*.{cs,vb}] + +# Naming rules + +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces +dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion +dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces +dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase + +dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion +dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters +dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase + +dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods +dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties +dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.events_should_be_pascalcase.symbols = events +dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables +dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase + +dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion +dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants +dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase + +dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion +dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters +dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase + +dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields +dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion +dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields +dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase + +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase + +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields +dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields +dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields +dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields +dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums +dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase + +dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase + +# Symbol specifications + +dotnet_naming_symbols.interfaces.applicable_kinds = interface +dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interfaces.required_modifiers = + +dotnet_naming_symbols.enums.applicable_kinds = enum +dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.enums.required_modifiers = + +dotnet_naming_symbols.events.applicable_kinds = event +dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.events.required_modifiers = + +dotnet_naming_symbols.methods.applicable_kinds = method +dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.methods.required_modifiers = + +dotnet_naming_symbols.properties.applicable_kinds = property +dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.properties.required_modifiers = + +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_fields.required_modifiers = + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_fields.required_modifiers = + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum +dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types_and_namespaces.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +dotnet_naming_symbols.type_parameters.applicable_kinds = namespace +dotnet_naming_symbols.type_parameters.applicable_accessibilities = * +dotnet_naming_symbols.type_parameters.required_modifiers = + +dotnet_naming_symbols.private_constant_fields.applicable_kinds = field +dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_constant_fields.required_modifiers = const + +dotnet_naming_symbols.local_variables.applicable_kinds = local +dotnet_naming_symbols.local_variables.applicable_accessibilities = local +dotnet_naming_symbols.local_variables.required_modifiers = + +dotnet_naming_symbols.local_constants.applicable_kinds = local +dotnet_naming_symbols.local_constants.applicable_accessibilities = local +dotnet_naming_symbols.local_constants.required_modifiers = const + +dotnet_naming_symbols.parameters.applicable_kinds = parameter +dotnet_naming_symbols.parameters.applicable_accessibilities = * +dotnet_naming_symbols.parameters.required_modifiers = + +dotnet_naming_symbols.public_constant_fields.applicable_kinds = field +dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_constant_fields.required_modifiers = const + +dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal +dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected +dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function +dotnet_naming_symbols.local_functions.applicable_accessibilities = * +dotnet_naming_symbols.local_functions.required_modifiers = + +# Naming styles + +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.capitalization = pascal_case + +dotnet_naming_style.ipascalcase.required_prefix = I +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.capitalization = pascal_case + +dotnet_naming_style.tpascalcase.required_prefix = T +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.capitalization = pascal_case + +dotnet_naming_style._camelcase.required_prefix = _ +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.capitalization = camel_case + +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.capitalization = camel_case + +dotnet_naming_style.s_camelcase.required_prefix = s_ +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.capitalization = camel_case + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 710eeb6..4a283f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,13 +9,13 @@ on: - '*' tags: - 'v[0-9]+.[0-9]+.[0-9]+.*' - pull_request: + pull_request: workflow_dispatch: env: VERSION: 0.0.0 ConnectionStrings__Test: ${{ secrets.TEST_CONNECTION_STRING }} - + jobs: build: runs-on: ubuntu-latest @@ -23,7 +23,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ github.ref }} # This checks out the commit that triggered the workflow run - + - name: Setup .NET uses: actions/setup-dotnet@v4 with: @@ -39,6 +39,9 @@ jobs: - name: Restore dependencies run: dotnet restore + - name: Verify Format + run: dotnet format --verify-no-changes --verbosity diagnostic + - name: Build run: dotnet build --no-restore --configuration Release /p:Version=${{ env.VERSION }} /p:CopyrightYear=$(date +%Y) @@ -85,7 +88,7 @@ jobs: with: name: build-artifact path: out - + - name: Setup .NET uses: actions/setup-dotnet@v4 with: diff --git a/Npgmq.Example/Program.cs b/Npgmq.Example/Program.cs index 49c31d4..780306b 100644 --- a/Npgmq.Example/Program.cs +++ b/Npgmq.Example/Program.cs @@ -1,6 +1,9 @@ using System.Reflection; + using Microsoft.Extensions.Configuration; + using Npgmq; + using Npgsql; var configuration = new ConfigurationBuilder() diff --git a/Npgmq.Test/NpgmqClientTest.cs b/Npgmq.Test/NpgmqClientTest.cs index 65e290b..48b0ed0 100644 --- a/Npgmq.Test/NpgmqClientTest.cs +++ b/Npgmq.Test/NpgmqClientTest.cs @@ -1,7 +1,11 @@ using System.Text.Json; + using Dapper; + using DeepEqual.Syntax; + using Microsoft.Extensions.Configuration; + using Npgsql; namespace Npgmq.Test; @@ -9,7 +13,7 @@ namespace Npgmq.Test; public sealed class NpgmqClientTest : IDisposable { private static readonly string TestQueueName = $"test_{Guid.NewGuid():N}"; - + private readonly string _connectionString; private readonly NpgsqlConnection _connection; private readonly NpgmqClient _sut; @@ -32,13 +36,13 @@ public NpgmqClientTest() _connection = new NpgsqlConnection(_connectionString); _sut = new NpgmqClient(_connection); } - + public void Dispose() { _connection.Close(); _connection.Dispose(); } - + private async Task ResetTestQueueAsync() { if (await _sut.QueueExistsAsync(TestQueueName)) @@ -60,7 +64,7 @@ public async Task NpgmqClient_should_work_when_created_using_a_connection_string // Arrange await ResetTestQueueAsync(); var sut = new NpgmqClient(_connectionString); // Don't use _sut here, as we want to test a new instance - + // Act await sut.SendAsync(TestQueueName, new TestMessage { Foo = 123 }); var msg = await sut.ReadAsync(TestQueueName); @@ -75,7 +79,7 @@ public async Task ArchiveAsync_should_archive_a_single_message() { // Arrange await ResetTestQueueAsync(); - + // Act var msgId = await _sut.SendAsync(TestQueueName, new TestMessage { @@ -92,13 +96,13 @@ public async Task ArchiveAsync_should_archive_a_single_message() Assert.Equal(1, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.a_{TestQueueName};")); Assert.Equal(msgId, await _connection.ExecuteScalarAsync($"SELECT msg_id FROM pgmq.a_{TestQueueName} LIMIT 1;")); } - + [Fact] public async Task ArchiveAsync_should_return_false_if_message_not_found() { // Arrange await ResetTestQueueAsync(); - + // Act var result = await _sut.ArchiveAsync(TestQueueName, 1); @@ -111,7 +115,7 @@ public async Task ArchiveBatchAsync_should_archive_multiple_messages() { // Arrange await ResetTestQueueAsync(); - + // Act var msgIds = new List { @@ -128,7 +132,7 @@ public async Task ArchiveBatchAsync_should_archive_multiple_messages() Assert.Equal(3, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.a_{TestQueueName};")); Assert.Equal(msgIds.OrderBy(x => x), (await _connection.QueryAsync($"SELECT msg_id FROM pgmq.a_{TestQueueName} ORDER BY msg_id;")).ToList()); } - + [Fact] public async Task CreateQueueAsync_should_create_a_queue() { @@ -137,14 +141,14 @@ public async Task CreateQueueAsync_should_create_a_queue() { await _sut.DropQueueAsync(TestQueueName); } - + // Act await _sut.CreateQueueAsync(TestQueueName); - + // Assert Assert.Equal(1, await _connection.ExecuteScalarAsync("SELECT count(*) FROM pgmq.meta WHERE queue_name = @queueName and is_partitioned = false and is_unlogged = false;", new { queueName = TestQueueName })); } - + [Fact] public async Task CreateUnloggedQueueAsync_should_create_an_unlogged_queue() { @@ -153,10 +157,10 @@ public async Task CreateUnloggedQueueAsync_should_create_an_unlogged_queue() { await _sut.DropQueueAsync(TestQueueName); } - + // Act await _sut.CreateUnloggedQueueAsync(TestQueueName); - + // Assert Assert.Equal(1, await _connection.ExecuteScalarAsync("SELECT count(*) FROM pgmq.meta WHERE queue_name = @queueName and is_partitioned = false and is_unlogged = true;", new { queueName = TestQueueName })); } @@ -166,7 +170,7 @@ public async Task DeleteAsync_should_delete_message() { // Arrange await ResetTestQueueAsync(); - + // Act var msgId = await _sut.SendAsync(TestQueueName, new TestMessage { @@ -182,20 +186,20 @@ public async Task DeleteAsync_should_delete_message() Assert.Equal(0, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.q_{TestQueueName};")); Assert.Equal(0, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.a_{TestQueueName};")); } - + [Fact] public async Task DeleteAsync_should_return_false_if_message_not_found() { // Arrange await ResetTestQueueAsync(); - + // Act var result = await _sut.DeleteAsync(TestQueueName, 1); // Assert Assert.False(result); } - + [Fact] public async Task DeleteBatchAsync_should_delete_multiple_messages() { @@ -208,7 +212,7 @@ public async Task DeleteBatchAsync_should_delete_multiple_messages() await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 3 }) }; Assert.Equal(3, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.q_{TestQueueName};")); - + // Act var results = await _sut.DeleteBatchAsync(TestQueueName, msgIds); @@ -224,7 +228,7 @@ public async Task DropQueueAsync_should_drop_queue() // Arrange await ResetTestQueueAsync(); Assert.Equal(1, await _connection.ExecuteScalarAsync("SELECT count(*) FROM pgmq.meta WHERE queue_name = @queueName;", new { queueName = TestQueueName })); - + // Act await _sut.DropQueueAsync(TestQueueName); @@ -266,10 +270,10 @@ public async Task GetPgmqVersionAsync_should_return_pgmq_version() // Arrange var expectedVersionString = await _connection.ExecuteScalarAsync("SELECT extversion FROM pg_extension WHERE extname = 'pgmq';"); var expectedVersion = new Version(expectedVersionString!); - + // Act var version = await _sut.GetPgmqVersionAsync(); - + // Assert Assert.Equal(expectedVersion, version); } @@ -301,23 +305,24 @@ public async Task ListQueuesAsync_should_return_list_of_queues() { // Arrange await ResetTestQueueAsync(); - + // Act var queues = await _sut.ListQueuesAsync(); // Assert var queue = Assert.Single(queues); Assert.Equal(TestQueueName, queue.QueueName); + Assert.True(queue.CreatedAt < DateTimeOffset.UtcNow); Assert.False(queue.IsPartitioned); Assert.False(queue.IsUnlogged); } - + [Fact] public async Task PollAsync_should_wait_for_message_and_return_it() { // Arrange await ResetTestQueueAsync(); - + // Act var pollTask = _sut.PollAsync(TestQueueName); await Task.Delay(1000); @@ -329,9 +334,9 @@ public async Task PollAsync_should_wait_for_message_and_return_it() Bar = "Test", Baz = DateTimeOffset.Parse("2023-09-01T01:23:45-04:00") }); - + var msg = await pollTask; - + // Assert Assert.NotNull(msg); Assert.True(msg.EnqueuedAt < DateTimeOffset.UtcNow); @@ -350,7 +355,7 @@ public async Task PollAsync_should_return_null_if_timeout_occurs_before_a_messag { // Arrange await ResetTestQueueAsync(); - + // Act var pollTask = _sut.PollAsync(TestQueueName, pollTimeoutSeconds: 1); await Task.Delay(1100); @@ -361,7 +366,7 @@ public async Task PollAsync_should_return_null_if_timeout_occurs_before_a_messag Baz = DateTimeOffset.Parse("2023-09-01T01:23:45-04:00") }); var msg = await pollTask; - + // Assert Assert.Null(msg); Assert.Equal(1, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.q_{TestQueueName};")); @@ -372,7 +377,7 @@ public async Task PollBatchAsync_should_poll_for_multiple_messages() { // Arrange await ResetTestQueueAsync(); - + // Act var pollTask = _sut.PollBatchAsync(TestQueueName, limit: 3); @@ -384,12 +389,12 @@ public async Task PollBatchAsync_should_poll_for_multiple_messages() await producer.SendAsync(TestQueueName, new TestMessage { Foo = 3 }); await producer.SendAsync(TestQueueName, new TestMessage { Foo = 4 }); await producer.SendAsync(TestQueueName, new TestMessage { Foo = 5 }); - + // Get the messages received by the poll var messages = await pollTask; - + // Assert - Assert.True(messages.Any()); + Assert.True(messages.Count > 0); Assert.True(messages.Count <= 3); // TODO: Improve this test, keeping in mind that each call to PollBatchAsync is not guaranteed to read the limit } @@ -419,19 +424,19 @@ public async Task PollBatchAsync_should_poll_for_multiple_messages_in_multiple_b var batch2 = await _sut.PollBatchAsync(TestQueueName, limit: 3, pollTimeoutSeconds: 1); // Assert - Assert.True(batch1.Any()); + Assert.True(batch1.Count > 0); Assert.True(batch1.Count <= 3); - Assert.True(batch2.Any()); + Assert.True(batch2.Count > 0); Assert.True(batch2.Count <= 3); Assert.Equal(batch1.Count + batch2.Count, batch1.Select(x => x.MsgId).Union(batch2.Select(x => x.MsgId)).Count()); } - + [Fact] public async Task PopAsync_should_read_and_delete_message() { // Arrange await ResetTestQueueAsync(); - + // Act var msgId = await _sut.SendAsync(TestQueueName, new TestMessage { @@ -463,7 +468,7 @@ public async Task PopAsync_should_return_null_if_no_message_is_available() { // Arrange await ResetTestQueueAsync(); - + // Act var msg = await _sut.PopAsync(TestQueueName); @@ -481,7 +486,7 @@ public async Task PurgeQueueAsync_should_delete_all_messages_from_a_queue() await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 2 }); await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 3 }); Assert.Equal(3, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.q_{TestQueueName};")); - + // Act var purgeCount = await _sut.PurgeQueueAsync(TestQueueName); @@ -495,34 +500,34 @@ public async Task QueueExistsAsync_should_return_true_if_queue_exists() { // Arrange await ResetTestQueueAsync(); - + // Act var result = await _sut.QueueExistsAsync(TestQueueName); // Assert Assert.True(result); } - + [Fact] public async Task QueueExistsAsync_should_return_false_if_queue_does_not_exist() { // Arrange await ResetTestQueueAsync(); await _sut.DropQueueAsync(TestQueueName); - + // Act var result = await _sut.QueueExistsAsync(TestQueueName); // Assert Assert.False(result); } - + [Fact] public async Task ReadAsync_should_read_message() { // Arrange await ResetTestQueueAsync(); - + // Act var msgId = await _sut.SendAsync(TestQueueName, new TestMessage { @@ -554,7 +559,7 @@ public async Task ReadAsync_should_read_string_message() { // Arrange await ResetTestQueueAsync(); - + // Act var msgId = await _sut.SendAsync(TestQueueName, new { @@ -581,7 +586,7 @@ public async Task ReadAsync_should_return_null_if_no_message_is_available() { // Arrange await ResetTestQueueAsync(); - + // Act var msg = await _sut.ReadAsync(TestQueueName); @@ -595,7 +600,7 @@ public async Task ReadBatchAsync_should_return_list_of_messages() { // Arrange await ResetTestQueueAsync(); - + // Act var msgIds = new List { @@ -642,7 +647,7 @@ public async Task SendAsync_should_add_message() { // Arrange await ResetTestQueueAsync(); - + // Act var msgId = await _sut.SendAsync(TestQueueName, new TestMessage { @@ -725,11 +730,11 @@ public async Task SendAsync_should_add_string_message() { // Arrange await ResetTestQueueAsync(); - + // Act var message = "{\"Foo\": 123, \"Bar\": \"Test\", \"Baz\": \"2023-09-01T01:23:45-04:00\"}"; - var msgId = await _sut.SendAsync(TestQueueName, message); - + var msgId = await _sut.SendAsync(TestQueueName, message); + // Assert Assert.Equal(1, await _connection.ExecuteScalarAsync($"SELECT count(*) FROM pgmq.q_{TestQueueName};")); Assert.Equal(msgId, await _connection.ExecuteScalarAsync($"SELECT msg_id FROM pgmq.q_{TestQueueName} LIMIT 1;")); @@ -742,7 +747,7 @@ public async Task SendAsync_with_delay_should_add_message_with_future_vt() { // Arrange await ResetTestQueueAsync(); - + // Act var msgId = await _sut.SendAsync(TestQueueName, new TestMessage { @@ -762,7 +767,7 @@ public async Task SendBatchAsync_should_add_multiple_messages() { // Arrange await ResetTestQueueAsync(); - + // Act var msgIds = await _sut.SendBatchAsync(TestQueueName, new List { @@ -781,7 +786,7 @@ public async Task SendBatchAsync_with_delay_should_add_multiple_messages_with_fu { // Arrange await ResetTestQueueAsync(); - + // Act var msgIds = await _sut.SendBatchAsync(TestQueueName, new List { @@ -806,11 +811,11 @@ public async Task SetVtAsync_should_change_vt_for_a_message() Assert.NotNull(message1); Assert.Equal(msgId, message1.MsgId); Assert.Null(await _sut.ReadAsync(TestQueueName)); - + // Act await _sut.SetVtAsync(TestQueueName, msgId, -60); var message2 = await _sut.ReadAsync(TestQueueName); - + // Assert Assert.NotNull(message2); Assert.Equal(msgId, message2.MsgId); @@ -820,17 +825,17 @@ public async Task SetVtAsync_should_change_vt_for_a_message() public async Task GetMetricsAsync_should_return_metrics_for_a_single_queue() { Skip.IfNot(await IsMinPgmqVersion("0.33.1"), "PGMQ versions before 0.33.1 have a bug in the total messages calculation."); - + // Arrange await ResetTestQueueAsync(); - + var metrics1 = await _sut.GetMetricsAsync(TestQueueName); await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 1 }); await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 2 }); var msgId3 = await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 3 }); var msgId4 = await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 4 }); await _sut.SendAsync(TestQueueName, new TestMessage { Foo = 5 }); - await _sut.DeleteAsync(TestQueueName, msgId3); + await _sut.DeleteAsync(TestQueueName, msgId3); await _sut.ArchiveAsync(TestQueueName, msgId4); // Act @@ -839,7 +844,7 @@ public async Task GetMetricsAsync_should_return_metrics_for_a_single_queue() var metrics3 = await _sut.GetMetricsAsync(TestQueueName); await _sut.PurgeQueueAsync(TestQueueName); var metrics4 = await _sut.GetMetricsAsync(TestQueueName); - + // Assert Assert.Equal(TestQueueName, metrics1.QueueName); Assert.Equal(0, metrics1.QueueLength); @@ -864,7 +869,7 @@ public async Task GetMetricsAsync_should_return_metrics_for_a_single_queue() Assert.Equal(5, metrics3.TotalMessages); Assert.True(metrics3.ScrapeTime < DateTimeOffset.UtcNow); Assert.Equal(TimeSpan.Zero, metrics1.ScrapeTime.Offset); - + Assert.Equal(TestQueueName, metrics4.QueueName); Assert.Equal(0, metrics4.QueueLength); Assert.Null(metrics1.NewestMessageAge); @@ -873,7 +878,7 @@ public async Task GetMetricsAsync_should_return_metrics_for_a_single_queue() Assert.True(metrics4.ScrapeTime < DateTimeOffset.UtcNow); Assert.Equal(TimeSpan.Zero, metrics1.ScrapeTime.Offset); } - + [Fact] public async Task GetMetricsAsync_should_return_metrics_for_all_queues() { @@ -910,4 +915,4 @@ public async Task GetMetricsAsync_should_return_metrics_for_all_queues() try { await _sut.DropQueueAsync(testMetricsQueueName3); } catch { /* ignored */ } } } -} +} \ No newline at end of file diff --git a/Npgmq.sln b/Npgmq.sln index 5f0d93a..6c9b945 100644 --- a/Npgmq.sln +++ b/Npgmq.sln @@ -9,6 +9,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE = LICENSE README.md = README.md global.json = global.json + .editorconfig = .editorconfig + .gitignore = .gitignore EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{8C37002D-05C6-4B1F-B4FC-C2F45C5E5328}" diff --git a/Npgmq/INpgmqClient.cs b/Npgmq/INpgmqClient.cs index dd5d8b3..4421bb8 100644 --- a/Npgmq/INpgmqClient.cs +++ b/Npgmq/INpgmqClient.cs @@ -32,7 +32,7 @@ public interface INpgmqClient /// /// The queue name. Task CreateUnloggedQueueAsync(string queueName); - + /// /// Delete a message. /// @@ -68,13 +68,13 @@ public interface INpgmqClient /// /// A object representing the version of the pgmq extension. Task GetPgmqVersionAsync(); - + /// /// List queues. /// /// The list of queues. Task> ListQueuesAsync(); - + /// /// Poll a queue for a message. /// @@ -85,7 +85,7 @@ public interface INpgmqClient /// The message type. /// The message read, or null if no message was read. Task?> PollAsync(string queue, int vt = NpgmqClient.DefaultVt, int pollTimeoutSeconds = NpgmqClient.DefaultPollTimeoutSeconds, int pollIntervalMilliseconds = NpgmqClient.DefaultPollIntervalMilliseconds) where T : class; - + /// /// Poll a queue for multiple messages. /// @@ -105,7 +105,7 @@ public interface INpgmqClient /// The message type. /// The message read, or null if no message was read. Task?> PopAsync(string queueName) where T : class; - + /// /// Purge a queue. /// @@ -119,7 +119,7 @@ public interface INpgmqClient /// The queue name. /// True if the queue exists, false otherwise. Task QueueExistsAsync(string queueName); - + /// /// Read a message from a queue. /// @@ -138,7 +138,7 @@ public interface INpgmqClient /// The message type. /// The messages read. Task>> ReadBatchAsync(string queue, int vt = NpgmqClient.DefaultVt, int limit = NpgmqClient.DefaultReadBatchLimit) where T : class; - + /// /// Send a message to a queue. /// @@ -147,7 +147,7 @@ public interface INpgmqClient /// The message type. /// The ID of the sent message. Task SendAsync(string queueName, T message) where T : class; - + /// /// Send a message to a queue, visible after a specified number of seconds. /// @@ -157,7 +157,7 @@ public interface INpgmqClient /// The message type. /// The ID of the sent message. Task SendAsync(string queueName, T message, int delay) where T : class; - + /// /// Send a message to a queue with a delayed vt. /// diff --git a/Npgmq/NpgmqClient.cs b/Npgmq/NpgmqClient.cs index 6e45f0e..fa8c714 100644 --- a/Npgmq/NpgmqClient.cs +++ b/Npgmq/NpgmqClient.cs @@ -1,7 +1,9 @@ using System.Data; using System.Data.Common; using System.Text.Json; + using Npgsql; + using NpgsqlTypes; namespace Npgmq; @@ -214,7 +216,7 @@ public async Task InitAsync() throw new NpgmqException("Failed to get PGMQ version.", ex); } } - + public async Task> ListQueuesAsync() { try @@ -378,9 +380,9 @@ public async Task>> ReadBatchAsync(string queueName, int } } - public Task SendAsync(string queueName, T message) where T : class => + public Task SendAsync(string queueName, T message) where T : class => SendAsync(queueName, message, 0); - + public async Task SendAsync(string queueName, T message, int delay) where T : class { try @@ -400,15 +402,15 @@ public async Task SendAsync(string queueName, T message, int delay) whe throw new NpgmqException($"Failed to send message to queue {queueName}.", ex); } } - + [Obsolete("Use SendAsync instead.")] public Task SendDelayAsync(string queueName, T message, int delay) where T : class => SendAsync(queueName, message, delay); public Task> SendBatchAsync(string queueName, IEnumerable messages) where T : class => SendBatchAsync(queueName, messages, 0); - - public async Task> SendBatchAsync(string queueName, IEnumerable messages, int delay) where T : class + + public async Task> SendBatchAsync(string queueName, IEnumerable messages, int delay) where T : class { try { @@ -501,7 +503,7 @@ public async Task GetMetricsAsync(string queueName) throw new NpgmqException($"Failed to get metrics for queue {queueName}.", ex); } } - + private static async Task> ReadMetricsAsync(DbDataReader reader) { var results = new List(); @@ -537,9 +539,9 @@ private static async Task>> ReadMessagesAsync(DbDataRead return result; } - private static string SerializeMessage(T message) where T : class => - typeof(T) == typeof(string) ? message as string ?? "" : JsonSerializer.Serialize(message); + private static string SerializeMessage(T message) where T : class => + typeof(T) == typeof(string) ? message as string ?? "" : JsonSerializer.Serialize(message); private static T? DeserializeMessage(string message) where T : class => typeof(T) == typeof(string) ? (T?)(object?)message : JsonSerializer.Deserialize(message); -} +} \ No newline at end of file diff --git a/Npgmq/NpgmqCommand.cs b/Npgmq/NpgmqCommand.cs index 08d286c..1304d63 100644 --- a/Npgmq/NpgmqCommand.cs +++ b/Npgmq/NpgmqCommand.cs @@ -1,4 +1,5 @@ using System.Data; + using Npgsql; namespace Npgmq; @@ -14,8 +15,8 @@ public override async ValueTask DisposeAsync() { await Connection.CloseAsync().ConfigureAwait(false); } - + await Connection.DisposeAsync().ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/Npgmq/NpgmqCommandFactory.cs b/Npgmq/NpgmqCommandFactory.cs index 49d385d..393fa35 100644 --- a/Npgmq/NpgmqCommandFactory.cs +++ b/Npgmq/NpgmqCommandFactory.cs @@ -1,4 +1,5 @@ using System.Data; + using Npgsql; namespace Npgmq; @@ -25,7 +26,7 @@ public async Task CreateAsync(string commandText) { await connection.OpenAsync().ConfigureAwait(false); } - + return new NpgmqCommand(commandText, connection, _connection == null); } -} +} \ No newline at end of file diff --git a/Npgmq/NpgmqException.cs b/Npgmq/NpgmqException.cs index 14e1086..52a1113 100644 --- a/Npgmq/NpgmqException.cs +++ b/Npgmq/NpgmqException.cs @@ -21,4 +21,4 @@ public NpgmqException(string message) : base(message) public NpgmqException(string message, Exception innerException) : base(message, innerException) { } -} +} \ No newline at end of file diff --git a/Npgmq/NpgmqMessage.cs b/Npgmq/NpgmqMessage.cs index c81ade1..3d2efec 100644 --- a/Npgmq/NpgmqMessage.cs +++ b/Npgmq/NpgmqMessage.cs @@ -10,12 +10,12 @@ public class NpgmqMessage /// Unique identifier for the message. /// public long MsgId { get; set; } - + /// /// The number of times the message has been read. Increments on read. /// public int ReadCt { get; set; } - + /// /// Timestamp at which the message was sent to the queue. /// @@ -25,7 +25,7 @@ public class NpgmqMessage /// Timestamp at which the message will be available for reading. /// public DateTimeOffset Vt { get; set; } - + /// /// The message value. /// diff --git a/Npgmq/NpgmqMetricsResult.cs b/Npgmq/NpgmqMetricsResult.cs index e06aa4b..4546216 100644 --- a/Npgmq/NpgmqMetricsResult.cs +++ b/Npgmq/NpgmqMetricsResult.cs @@ -9,27 +9,27 @@ public class NpgmqMetricsResult /// Name of the queue. /// public string QueueName { get; set; } = null!; - + /// /// Number of messages in the queue. /// public long QueueLength { get; set; } - + /// /// Age, in seconds, of the newest message in the queue, or null if the queue is empty. /// public int? NewestMessageAge { get; set; } - + /// /// Age, in seconds, of the oldest message in the queue, or null if the queue is empty. /// public int? OldestMessageAge { get; set; } - + /// /// Total number of messages that have been in the queue. /// public long TotalMessages { get; set; } - + /// /// When the metrics were scraped. /// diff --git a/Npgmq/NpgmqQueue.cs b/Npgmq/NpgmqQueue.cs index 1e00ffe..68b7a09 100644 --- a/Npgmq/NpgmqQueue.cs +++ b/Npgmq/NpgmqQueue.cs @@ -9,17 +9,17 @@ public class NpgmqQueue /// The name of the queue. /// public string QueueName { get; set; } = null!; - + /// /// Timestamp at which the queue was created. /// public DateTimeOffset CreatedAt { get; set; } - + /// /// Whether the queue is partitioned. /// public bool IsPartitioned { get; set; } - + /// /// Whether the queue is unlogged. ///