From 26bf2bbbd16dce62971d5586708602605752b28e Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Fri, 3 May 2024 18:49:51 -0600 Subject: [PATCH] switched to xunit --- .editorconfig | 450 ++++++++++++------ Management.Test/CanvasModels/TermTests.cs | 2 +- .../Features/CalendarMonthTests.cs | 8 +- .../Features/ConfigurationTests.cs | 2 +- Management.Test/Features/ModuleTests.cs | 4 +- .../Features/SemesterPlannerTests.cs | 12 +- Management.Test/Management.Test.csproj | 20 +- .../Markdown/AssignmentMarkdownTests.cs | 12 +- Management.Test/Markdown/FileStorageTests.cs | 27 +- Management.Test/Markdown/PageMarkdownTests.cs | 2 +- .../Markdown/Quiz/MatchingTests.cs | 6 +- .../Markdown/Quiz/MultipleAnswersTests.cs | 8 +- .../Markdown/Quiz/MultipleChoiceTests.cs | 4 +- .../Markdown/Quiz/QuizDeterministicChecks.cs | 14 +- .../Markdown/Quiz/QuizMarkdownTests.cs | 18 +- .../Markdown/Quiz/TextAnswerTests.cs | 8 +- .../Markdown/RubricMarkdownTests.cs | 16 +- .../Services/CanvasServiceTests.cs | 4 +- Management.Test/Usings.cs | 2 +- .../ViewModels/MonthDetailTests.cs | 2 +- Management.Web/Management.Web.csproj | 2 + Management.Web/Program.cs | 51 +- .../Shared/InitializeNewCourse.razor | 2 +- Management/Management.csproj | 5 + Management/Services/Actors/CanvasQueue.cs | 9 + .../Services/Actors/CanvasQueueActor.cs | 18 + .../Services/Actors/LocalStorageActor.cs | 61 +++ .../Services/Actors/LocalStorageCache.cs | 23 + Management/Services/AkkaService.cs | 60 +++ Management/Services/Canvas/CanvasService.cs | 3 +- .../Services/Files/FileStorageManager.cs | 4 +- .../Files/FileStorageManagerCached.cs | 78 +-- .../Services/Files/IFileStorageManager.cs | 2 +- README.md | 2 + 34 files changed, 643 insertions(+), 298 deletions(-) create mode 100644 Management/Services/Actors/CanvasQueue.cs create mode 100644 Management/Services/Actors/CanvasQueueActor.cs create mode 100644 Management/Services/Actors/LocalStorageActor.cs create mode 100644 Management/Services/Actors/LocalStorageCache.cs create mode 100644 Management/Services/AkkaService.cs diff --git a/.editorconfig b/.editorconfig index 65488a6..1e15504 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,50 +1,127 @@ -# Top-most EditorConfig file root = true -# use unix newline characters -end_of_line = lf - -[*.cs] -dotnet_naming_rule.methods_must_be_camel_case.severity = warning -dotnet_naming_rule.methods_must_be_camel_case.symbols = private_methods -dotnet_naming_rule.methods_must_be_camel_case.style = camel_case_style - -dotnet_naming_symbols.private_methods.applicable_kinds = method -dotnet_naming_symbols.private_methods.applicable_accessibilities = private - -dotnet_naming_style.camel_case_style.capitalization = camel_case -dotnet_diagnostic.CA2254.severity = none - -[*.razor] -indent_style = ignore -indent_size = ignore - - -# Default settings: -# A newline ending every file -# Use 4 spaces as indentation +# All files [*] -insert_final_newline = true indent_style = space indent_size = 2 -trim_trailing_whitespace = true - -[project.json] -indent_size = 2 - -# Generated code -[*{_AssemblyInfo.cs,.notsupported.cs}] -generated_code = true +end_of_line = lf # C# files [*.cs] +tab_width = 2 +insert_final_newline = false + +[*.{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_open_brace = all -csharp_new_line_before_else = true csharp_new_line_before_catch = true +csharp_new_line_before_else = true csharp_new_line_before_finally = true -csharp_new_line_before_members_in_object_initializers = 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 @@ -52,100 +129,8 @@ csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_case_contents_when_block = true -csharp_indent_switch_labels = true csharp_indent_labels = one_less_than_current - -# Modifier preferences -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion - -# avoid this. unless absolutely necessary -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_property = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_event = false:suggestion - -# Types: use keywords instead of BCL types, and permit var only when the type is clear -# csharp_style_var_for_built_in_types = false:suggestion -csharp_style_var_when_type_is_apparent = false:none -# csharp_style_var_elsewhere = false:suggestion -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion - -# name all constant fields using PascalCase -dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion -dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields -dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style -dotnet_naming_symbols.constant_fields.applicable_kinds = field -dotnet_naming_symbols.constant_fields.required_modifiers = const -dotnet_naming_style.pascal_case_style.capitalization = pascal_case - -# static fields should have s_ prefix -# dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -# dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -# dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style -# dotnet_naming_symbols.static_fields.applicable_kinds = field -# dotnet_naming_symbols.static_fields.required_modifiers = static -# dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected -# dotnet_naming_style.static_prefix_style.required_prefix = s_ -# dotnet_naming_style.static_prefix_style.capitalization = camel_case - -# internal and private fields should be _camelCase -dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion -dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields -dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style -dotnet_naming_symbols.private_internal_fields.applicable_kinds = field -dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal -dotnet_naming_style.camel_case_underscore_style.required_prefix = _ -dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case - -# Code style defaults -csharp_using_directive_placement = outside_namespace:suggestion -dotnet_sort_system_directives_first = true -csharp_prefer_braces = true:silent -csharp_preserve_single_line_blocks = true:none -csharp_preserve_single_line_statements = false:none -csharp_prefer_static_local_function = true:suggestion -csharp_prefer_simple_using_statement = false:none -csharp_style_prefer_switch_expression = true:suggestion -dotnet_style_readonly_field = true:suggestion - -# Expression-level preferences -dotnet_style_object_initializer = true:suggestion -dotnet_style_collection_initializer = true:suggestion -dotnet_style_explicit_tuple_names = true:suggestion -dotnet_style_coalesce_expression = true:suggestion -dotnet_style_null_propagation = true:suggestion -dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion -dotnet_style_prefer_inferred_tuple_names = true:suggestion -dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion -dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:silent -dotnet_style_prefer_conditional_expression_over_return = true:silent -csharp_prefer_simple_default_expression = true:suggestion - -# Expression-bodied members -csharp_style_expression_bodied_methods = true:silent -csharp_style_expression_bodied_constructors = true:silent -csharp_style_expression_bodied_operators = true:silent -csharp_style_expression_bodied_properties = true:silent -csharp_style_expression_bodied_indexers = true:silent -csharp_style_expression_bodied_accessors = true:silent -csharp_style_expression_bodied_lambdas = true:silent -csharp_style_expression_bodied_local_functions = true:silent - -# Pattern matching -csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion -csharp_style_pattern_matching_over_as_with_null_check = true:suggestion -csharp_style_inlined_variable_declaration = true:suggestion - -# Null checking preferences -csharp_style_throw_expression = true:suggestion -csharp_style_conditional_delegate_call = true:suggestion - -# Other features -csharp_style_prefer_index_operator = false:none -csharp_style_prefer_range_operator = false:none -csharp_style_pattern_local_over_anonymous_function = false:none +csharp_indent_switch_labels = true # Space preferences csharp_space_after_cast = false @@ -155,7 +140,7 @@ 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 = do_not_ignore +csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false @@ -171,34 +156,193 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false -# C++ Files -[*.{cpp,h,in}] -curly_bracket_next_line = true -indent_brace_style = Allman +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true -# Xml project files -[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] -indent_size = 2 +#### Naming styles #### +[*.{cs,vb}] -[*.{csproj,vbproj,proj,nativeproj,locproj}] -charset = utf-8 +# Naming rules -# Xml build files -[*.builds] -indent_size = 2 +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 -# Xml files -[*.{xml,stylecop,resx,ruleset}] -indent_size = 2 +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 -# Xml config files -[*.{props,targets,config,nuspec}] -indent_size = 2 +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 -# YAML config files -[*.{yml,yaml}] -indent_size = 2 +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_s_camelcase.severity = suggestion +dotnet_naming_rule.private_fields_should_be_s_camelcase.symbols = private_fields +dotnet_naming_rule.private_fields_should_be_s_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.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 -# Shell scripts -[*.sh] -end_of_line = lf diff --git a/Management.Test/CanvasModels/TermTests.cs b/Management.Test/CanvasModels/TermTests.cs index f8834ea..a348d11 100644 --- a/Management.Test/CanvasModels/TermTests.cs +++ b/Management.Test/CanvasModels/TermTests.cs @@ -3,7 +3,7 @@ using CanvasModel.EnrollmentTerms; public class DeserializationTests { - [Test] + [Fact] public void TestTerm() { diff --git a/Management.Test/Features/CalendarMonthTests.cs b/Management.Test/Features/CalendarMonthTests.cs index dfd1373..3d4e4c5 100644 --- a/Management.Test/Features/CalendarMonthTests.cs +++ b/Management.Test/Features/CalendarMonthTests.cs @@ -1,6 +1,6 @@ public class CalendarMonthTests { - [Test] + [Fact] public void TestCalendarMonthCanGetFirstWeek() { var month = new CalendarMonth(2023, 2); @@ -12,7 +12,7 @@ public class CalendarMonthTests month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek); } - [Test] + [Fact] public void TestCanGetAnotherMonthsFirstWeek() { var month = new CalendarMonth(2023, 4); @@ -24,7 +24,7 @@ public class CalendarMonthTests month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek); } - [Test] + [Fact] public void TestCorrectNumberOfWeeks() { var month = new CalendarMonth(2023, 4); @@ -32,7 +32,7 @@ public class CalendarMonthTests month.Weeks.Count().Should().Be(6); } - [Test] + [Fact] public void TestLastWeekIsCorrect() { var month = new CalendarMonth(2023, 4); diff --git a/Management.Test/Features/ConfigurationTests.cs b/Management.Test/Features/ConfigurationTests.cs index d331aed..2276028 100644 --- a/Management.Test/Features/ConfigurationTests.cs +++ b/Management.Test/Features/ConfigurationTests.cs @@ -2,7 +2,7 @@ // public class ConfigurationTests // { -// [Test] +// [Fact] // public void TestCanCreateConfigFromTermAndDays() // { // DateTime startAt = new DateTime(2022, 1, 1); diff --git a/Management.Test/Features/ModuleTests.cs b/Management.Test/Features/ModuleTests.cs index 3c8d570..732ac41 100644 --- a/Management.Test/Features/ModuleTests.cs +++ b/Management.Test/Features/ModuleTests.cs @@ -1,6 +1,6 @@ // public class ModuleTests // { -// [Test] +// [Fact] // public void CanAddModule() // { // var manager = new ModuleManager(); @@ -11,7 +11,7 @@ // manager.Modules.First().Should().Be(module); // } -// [Test] +// [Fact] // public void CanAddAssignmentToCorrectModule() // { // var manager = new ModuleManager(); diff --git a/Management.Test/Features/SemesterPlannerTests.cs b/Management.Test/Features/SemesterPlannerTests.cs index 1dedad0..6409eb4 100644 --- a/Management.Test/Features/SemesterPlannerTests.cs +++ b/Management.Test/Features/SemesterPlannerTests.cs @@ -4,7 +4,7 @@ // public class SemesterPlannerTests // { -// [Test] +// [Fact] // public void TestCanCreatePlanner() // { @@ -19,7 +19,7 @@ // semester.Months.Count().Should().Be(1); // } -// [Test] +// [Fact] // public void TestNewPlannerHasCorrectNumberOfMonths() // { // var config = new SemesterCalendarConfig( @@ -33,7 +33,7 @@ // semester.Months.Count().Should().Be(2); // } -// [Test] +// [Fact] // public void TestNewPlannerHandlesTermsThatWrapYears() // { // var config = new SemesterCalendarConfig( @@ -47,7 +47,7 @@ // semester.Months.Count().Should().Be(2); // } -// [Test] +// [Fact] // public void TestSemesterGetsCorrectMonths() // { // var config = new SemesterCalendarConfig( @@ -63,7 +63,7 @@ // } -// [Test] +// [Fact] // public void TestMonthsCanWrapYears() // { // var config = new SemesterCalendarConfig( @@ -81,7 +81,7 @@ // semester.Months.Last().Year.Should().Be(2023); // } -// [Test] +// [Fact] // public void TestSemesterTracksDaysOfWeek() // { // DayOfWeek[] days = new DayOfWeek[] { DayOfWeek.Monday }; diff --git a/Management.Test/Management.Test.csproj b/Management.Test/Management.Test.csproj index abf1b49..f9c8e43 100644 --- a/Management.Test/Management.Test.csproj +++ b/Management.Test/Management.Test.csproj @@ -9,15 +9,25 @@ + + - - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/Management.Test/Markdown/AssignmentMarkdownTests.cs b/Management.Test/Markdown/AssignmentMarkdownTests.cs index 2ce5c60..941f7eb 100644 --- a/Management.Test/Markdown/AssignmentMarkdownTests.cs +++ b/Management.Test/Markdown/AssignmentMarkdownTests.cs @@ -2,7 +2,7 @@ using LocalModels; public class AssignmentMarkdownTests { - [Test] + [Fact] public void TestCanParseAssignmentSettings() { var assignment = new LocalAssignment() @@ -24,7 +24,7 @@ public class AssignmentMarkdownTests var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); parsedAssignment.Should().BeEquivalentTo(assignment); } - [Test] + [Fact] public void AssignmentWithEmptyRubric_CanBeParsed() { var assignment = new LocalAssignment() @@ -43,7 +43,7 @@ public class AssignmentMarkdownTests var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); parsedAssignment.Should().BeEquivalentTo(assignment); } - [Test] + [Fact] public void AssignmentWithEmptySubmissionTypes_CanBeParsed() { var assignment = new LocalAssignment() @@ -66,7 +66,7 @@ public class AssignmentMarkdownTests parsedAssignment.Should().BeEquivalentTo(assignment); } - [Test] + [Fact] public void AssignmentWithoutLockAtDate_CanBeParsed() { var assignment = new LocalAssignment() @@ -89,7 +89,7 @@ public class AssignmentMarkdownTests parsedAssignment.Should().BeEquivalentTo(assignment); } - [Test] + [Fact] public void AssignmentWithoutDescription_CanBeParsed() { var assignment = new LocalAssignment() @@ -111,7 +111,7 @@ public class AssignmentMarkdownTests var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); parsedAssignment.Should().BeEquivalentTo(assignment); } - [Test] + [Fact] public void Assignments_CanHaveThreeDashes() { var assignment = new LocalAssignment() diff --git a/Management.Test/Markdown/FileStorageTests.cs b/Management.Test/Markdown/FileStorageTests.cs index e72056e..038d608 100644 --- a/Management.Test/Markdown/FileStorageTests.cs +++ b/Management.Test/Markdown/FileStorageTests.cs @@ -5,14 +5,13 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; -using NUnit.Framework.Internal; public class FileStorageTests { - private IFileStorageManager fileManager { get; set; } + private FileStorageManager fileManager { get; set; } - private static string setupTempDirectory() + public FileStorageTests() { var tempDirectory = Path.GetTempPath(); var storageDirectory = tempDirectory + "fileStorageTests"; @@ -29,14 +28,6 @@ public class FileStorageTests dir.Delete(true); } - return storageDirectory; - } - - [SetUp] - public void SetUp() - { - var storageDirectory = setupTempDirectory(); - var fileManagerLogger = new MyLogger(NullLogger.Instance); var markdownLoaderLogger = new MyLogger(NullLogger.Instance); var markdownSaverLogger = new MyLogger(NullLogger.Instance); @@ -52,7 +43,7 @@ public class FileStorageTests fileManager = new FileStorageManager(fileManagerLogger, markdownLoader, markdownSaver, otherLogger, fileConfiguration); } - [Test] + [Fact] public async Task EmptyCourse_CanBeSavedAndLoaded() { LocalCourse testCourse = new LocalCourse @@ -69,7 +60,7 @@ public class FileStorageTests loadedCourse.Should().BeEquivalentTo(testCourse); } - [Test] + [Fact] public async Task CourseSettings_CanBeSavedAndLoaded() { LocalCourse testCourse = new() @@ -95,7 +86,7 @@ public class FileStorageTests } - [Test] + [Fact] public async Task EmptyCourseModules_CanBeSavedAndLoaded() { LocalCourse testCourse = new() @@ -119,7 +110,7 @@ public class FileStorageTests loadedCourse.Modules.Should().BeEquivalentTo(testCourse.Modules); } - [Test] + [Fact] public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded() { LocalCourse testCourse = new() @@ -160,7 +151,7 @@ public class FileStorageTests } - [Test] + [Fact] public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded() { LocalCourse testCourse = new() @@ -204,7 +195,7 @@ public class FileStorageTests } - [Test] + [Fact] public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData() { LocalCourse testCourse = new() @@ -271,7 +262,7 @@ public class FileStorageTests } - [Test] + [Fact] public async Task MarkdownStorage_CanPersistPages() { LocalCourse testCourse = new() diff --git a/Management.Test/Markdown/PageMarkdownTests.cs b/Management.Test/Markdown/PageMarkdownTests.cs index cd5a802..523509e 100644 --- a/Management.Test/Markdown/PageMarkdownTests.cs +++ b/Management.Test/Markdown/PageMarkdownTests.cs @@ -2,7 +2,7 @@ using LocalModels; public class PageMarkdownTests { - [Test] + [Fact] public void TestCanParsePage() { var page = new LocalCoursePage diff --git a/Management.Test/Markdown/Quiz/MatchingTests.cs b/Management.Test/Markdown/Quiz/MatchingTests.cs index fa78559..1a66e5f 100644 --- a/Management.Test/Markdown/Quiz/MatchingTests.cs +++ b/Management.Test/Markdown/Quiz/MatchingTests.cs @@ -2,7 +2,7 @@ using LocalModels; public class MatchingTests { - [Test] + [Fact] public void CanParseMatchingQuestion() { var rawMarkdownQuiz = @" @@ -29,7 +29,7 @@ Match the following terms & definitions firstQuestion.Answers.First().MatchedText.Should().Be("a single command to be executed"); } - [Test] + [Fact] public void CanCreateMarkdownForMatchingQuesiton() { var rawMarkdownQuiz = @" @@ -59,7 +59,7 @@ Match the following terms & definitions ^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)"; questionMarkdown.Should().Contain(expectedMarkdown); } - [Test] + [Fact] public void WhitespaceIsOptional() { var rawMarkdownQuiz = @" diff --git a/Management.Test/Markdown/Quiz/MultipleAnswersTests.cs b/Management.Test/Markdown/Quiz/MultipleAnswersTests.cs index e9a123d..e297ade 100644 --- a/Management.Test/Markdown/Quiz/MultipleAnswersTests.cs +++ b/Management.Test/Markdown/Quiz/MultipleAnswersTests.cs @@ -3,7 +3,7 @@ using LocalModels; public class MultipleAnswersTests { - [Test] + [Fact] public void QuzMarkdownIncludesMultipleAnswerQuestion() { var quiz = new LocalQuiz() @@ -43,7 +43,7 @@ oneline question markdown.Should().Contain(expectedQuestionString); } - [Test] + [Fact] public void CanParseQuestionWithMultipleAnswers() { var rawMarkdownQuiz = @" @@ -81,7 +81,7 @@ Which events are triggered when the user clicks on an input field? } - [Test] + [Fact] public void CanUseBracesInAnswerFormultipleAnswer() { var rawMarkdownQuestion = @" @@ -95,7 +95,7 @@ Which events are triggered when the user clicks on an input field? question.Answers.Count().Should().Be(2); } - [Test] + [Fact] public void CanUseBracesInAnswerFormultipleAnswer_MultiLine() { var rawMarkdownQuestion = @" diff --git a/Management.Test/Markdown/Quiz/MultipleChoiceTests.cs b/Management.Test/Markdown/Quiz/MultipleChoiceTests.cs index 715076d..39d366f 100644 --- a/Management.Test/Markdown/Quiz/MultipleChoiceTests.cs +++ b/Management.Test/Markdown/Quiz/MultipleChoiceTests.cs @@ -2,7 +2,7 @@ using LocalModels; public class MultipleChoiceTests { - [Test] + [Fact] public void QuzMarkdownIncludesMultipleChoiceQuestion() { var quiz = new LocalQuiz() @@ -58,7 +58,7 @@ endline } - [Test] + [Fact] public void LetterOptionalForMultipleChoice() { diff --git a/Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs b/Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs index 825bbdf..a675b6c 100644 --- a/Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs +++ b/Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs @@ -4,7 +4,7 @@ using LocalModels; public class QuizDeterministicChecks { - [Test] + [Fact] public void SerializationIsDeterministic_EmptyQuiz() { var quiz = new LocalQuiz() @@ -23,7 +23,7 @@ public class QuizDeterministicChecks parsedQuiz.Should().BeEquivalentTo(quiz); } - [Test] + [Fact] public void SerializationIsDeterministic_ShowCorrectAnswers() { var quiz = new LocalQuiz() @@ -43,7 +43,7 @@ public class QuizDeterministicChecks parsedQuiz.Should().BeEquivalentTo(quiz); } - [Test] + [Fact] public void SerializationIsDeterministic_ShortAnswer() { var quiz = new LocalQuiz() @@ -71,7 +71,7 @@ public class QuizDeterministicChecks parsedQuiz.Should().BeEquivalentTo(quiz); } - [Test] + [Fact] public void SerializationIsDeterministic_Essay() { var quiz = new LocalQuiz() @@ -99,7 +99,7 @@ public class QuizDeterministicChecks parsedQuiz.Should().BeEquivalentTo(quiz); } - [Test] + [Fact] public void SerializationIsDeterministic_MultipleAnswer() { var quiz = new LocalQuiz() @@ -138,7 +138,7 @@ public class QuizDeterministicChecks parsedQuiz.Should().BeEquivalentTo(quiz); } - [Test] + [Fact] public void SerializationIsDeterministic_MultipleChoice() { var quiz = new LocalQuiz() @@ -176,7 +176,7 @@ public class QuizDeterministicChecks var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); parsedQuiz.Should().BeEquivalentTo(quiz); } - [Test] + [Fact] public void SerializationIsDeterministic_Matching() { var quiz = new LocalQuiz() diff --git a/Management.Test/Markdown/Quiz/QuizMarkdownTests.cs b/Management.Test/Markdown/Quiz/QuizMarkdownTests.cs index 7ec7ce4..9f1116e 100644 --- a/Management.Test/Markdown/Quiz/QuizMarkdownTests.cs +++ b/Management.Test/Markdown/Quiz/QuizMarkdownTests.cs @@ -4,7 +4,7 @@ using LocalModels; // try to follow syntax from https://github.com/gpoore/text2qti public class QuizMarkdownTests { - [Test] + [Fact] public void CanSerializeQuizToMarkdown() { var quiz = new LocalQuiz() @@ -37,7 +37,7 @@ this is my description in markdown } - [Test] + [Fact] public void TestCanParseMarkdownQuizWithNoQuestions() { var rawMarkdownQuiz = new StringBuilder(); @@ -68,7 +68,7 @@ this is my description in markdown quiz.AllowedAttempts.Should().Be(-1); quiz.Description.Should().Be(expectedDescription.ToString()); } - [Test] + [Fact] public void TestCanParseMarkdownQuizPassword() { @@ -94,7 +94,7 @@ this is my description in markdown quiz.Password.Should().Be(password); } - [Test] + [Fact] public void TestCanParseMarkdownQuiz_CanConfigureToShowCorrectAnswers() { var rawMarkdownQuiz = new StringBuilder(); @@ -118,7 +118,7 @@ this is my description in markdown quiz.showCorrectAnswers.Should().BeFalse(); } - [Test] + [Fact] public void TestCanParseQuizWithQuestions() { var rawMarkdownQuiz = @" @@ -159,7 +159,7 @@ b) false firstQuestion.Answers.ElementAt(1).Text.Should().Contain("endline"); } - [Test] + [Fact] public void CanParseMultipleQuestions() { var rawMarkdownQuiz = @" @@ -192,7 +192,7 @@ b) false secondQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_CHOICE); } - [Test] + [Fact] public void ShortAnswerToMarkdown_IsCorrect() { var rawMarkdownQuiz = @" @@ -220,7 +220,7 @@ Which events are triggered when the user clicks on an input field? short_answer"; questionMarkdown.Should().Contain(expectedMarkdown); } - [Test] + [Fact] public void NegativePoints_IsAllowed() { var rawMarkdownQuiz = @" @@ -244,7 +244,7 @@ short answer var firstQuestion = quiz.Questions.First(); firstQuestion.Points.Should().Be(-4); } - [Test] + [Fact] public void FloatingPointPoints_IsAllowed() { var rawMarkdownQuiz = @" diff --git a/Management.Test/Markdown/Quiz/TextAnswerTests.cs b/Management.Test/Markdown/Quiz/TextAnswerTests.cs index af49a21..f4dbe69 100644 --- a/Management.Test/Markdown/Quiz/TextAnswerTests.cs +++ b/Management.Test/Markdown/Quiz/TextAnswerTests.cs @@ -2,7 +2,7 @@ using LocalModels; public class TextAnswerTests { - [Test] + [Fact] public void CanParseEssay() { var rawMarkdownQuiz = @" @@ -28,7 +28,7 @@ essay firstQuestion.Text.Should().NotContain("essay"); } - [Test] + [Fact] public void CanParseShortAnswer() { var rawMarkdownQuiz = @" @@ -54,7 +54,7 @@ short answer firstQuestion.Text.Should().NotContain("short answer"); } - [Test] + [Fact] public void ShortAnswerToMarkdown_IsCorrect() { var rawMarkdownQuiz = @" @@ -83,7 +83,7 @@ short_answer"; questionMarkdown.Should().Contain(expectedMarkdown); } - [Test] + [Fact] public void EssayQuestionToMarkdown_IsCorrect() { var rawMarkdownQuiz = @" diff --git a/Management.Test/Markdown/RubricMarkdownTests.cs b/Management.Test/Markdown/RubricMarkdownTests.cs index 542806c..23b0aa4 100644 --- a/Management.Test/Markdown/RubricMarkdownTests.cs +++ b/Management.Test/Markdown/RubricMarkdownTests.cs @@ -3,7 +3,7 @@ using LocalModels; public class RubricMarkdownTests { - [Test] + [Fact] public void TestCanParseOneItem() { var rawRubric = @" @@ -17,7 +17,7 @@ public class RubricMarkdownTests rubric.First().Points.Should().Be(2); } - [Test] + [Fact] public void TestCanParseMultipleItems() { var rawRubric = @" @@ -32,7 +32,7 @@ public class RubricMarkdownTests rubric.ElementAt(1).Points.Should().Be(3); } - [Test] + [Fact] public void TestCanParseSinglePoint() { var rawRubric = @" @@ -45,7 +45,7 @@ public class RubricMarkdownTests rubric.First().Points.Should().Be(1); } - [Test] + [Fact] public void TestCanParseSingleExtraCredit_LowerCase() { var rawRubric = @" @@ -57,7 +57,7 @@ public class RubricMarkdownTests rubric.First().Label.Should().Be("(extra credit) this is the task"); } - [Test] + [Fact] public void TestCanParseSingleExtraCredit_UpperCase() { var rawRubric = @" @@ -69,7 +69,7 @@ public class RubricMarkdownTests rubric.First().Label.Should().Be("(Extra Credit) this is the task"); } - [Test] + [Fact] public void TestCanParseFloatingPointNubmers() { var rawRubric = @" @@ -79,7 +79,7 @@ public class RubricMarkdownTests var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); rubric.First().Points.Should().Be(1.5); } - [Test] + [Fact] public void TestCanParseNegativeNubmers() { var rawRubric = @" @@ -89,7 +89,7 @@ public class RubricMarkdownTests var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); rubric.First().Points.Should().Be(-2.0); } - [Test] + [Fact] public void TestCanParseNegativeFloatingPointNubmers() { var rawRubric = @" diff --git a/Management.Test/Services/CanvasServiceTests.cs b/Management.Test/Services/CanvasServiceTests.cs index 1504306..d0dc1fe 100644 --- a/Management.Test/Services/CanvasServiceTests.cs +++ b/Management.Test/Services/CanvasServiceTests.cs @@ -9,7 +9,7 @@ // public class ICanvasServiceTests // { -// [Test] +// [Fact] // public async Task CanReadCanvasSemesters() // { // var expectedTerms = new EnrollmentTermModel[] { @@ -28,7 +28,7 @@ // canvasTerms.Should().BeEquivalentTo(expectedTerms); // } -// [Test] +// [Fact] // public async Task CanGetActiveTerms() // { // var expectedTerms = new EnrollmentTermModel[] { diff --git a/Management.Test/Usings.cs b/Management.Test/Usings.cs index 41fe2b8..600fb6d 100644 --- a/Management.Test/Usings.cs +++ b/Management.Test/Usings.cs @@ -1,3 +1,3 @@ global using System.Text.Json; global using FluentAssertions; -global using NUnit.Framework; +global using Xunit; diff --git a/Management.Test/ViewModels/MonthDetailTests.cs b/Management.Test/ViewModels/MonthDetailTests.cs index 6969a2b..4da2ff8 100644 --- a/Management.Test/ViewModels/MonthDetailTests.cs +++ b/Management.Test/ViewModels/MonthDetailTests.cs @@ -2,7 +2,7 @@ using Management.Web.Pages.Course.CourseCalendar; public class MonthDetailTests { - [Test] + [Fact] public void TestCanGetMonthName() { var calendarMonth = new CalendarMonth(2022, 2); diff --git a/Management.Web/Management.Web.csproj b/Management.Web/Management.Web.csproj index 510e607..ca8c602 100644 --- a/Management.Web/Management.Web.csproj +++ b/Management.Web/Management.Web.csproj @@ -5,6 +5,8 @@ + + diff --git a/Management.Web/Program.cs b/Management.Web/Program.cs index 1d6e128..1dec766 100644 --- a/Management.Web/Program.cs +++ b/Management.Web/Program.cs @@ -1,19 +1,25 @@ global using System.ComponentModel.DataAnnotations; global using System.Text.Json; global using System.Text.Json.Serialization; + global using CanvasModel; global using CanvasModel.Courses; global using CanvasModel.EnrollmentTerms; + global using LocalModels; + global using Management.Planner; global using Management.Services; global using Management.Services.Canvas; global using Management.Web.Shared; global using Management.Web.Shared.Components; + using dotenv.net; + using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.ResponseCompression; + using OpenTelemetry; using OpenTelemetry.Logs; using OpenTelemetry.Metrics; @@ -69,25 +75,40 @@ builder.Services.AddServerSideBlazor(); builder.Services.AddLogging(); -builder.Services.AddScoped(typeof(MyLogger<>)); +builder.Services.AddSingleton(typeof(MyLogger<>)); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); +// stateless services +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); -builder.Services.AddScoped(); -builder.Services.AddScoped(); -builder.Services.AddScoped(sp => +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +builder.Services.AddSingleton(); + +// one actor system, maybe different actor for different pages? +builder.Services.AddSingleton(); +builder.Services.AddHostedService(sp => sp.GetRequiredService()); + + +// TODO: need to handle scoped requirements +builder.Services.AddSingleton(sp => { - var manager = ActivatorUtilities.CreateInstance(sp); - var logger = sp.GetRequiredService>(); - return new FileStorageManagerCached(manager, logger); + var akka = sp.GetRequiredService(); + return new CanvasQueue(akka.CanvasQueueActor ?? throw new Exception("Canvas queue actor not properly created")); }); +builder.Services.AddSingleton(sp => +{ + var akka = sp.GetRequiredService(); + return new LocalStorageCache(akka.StorageActor ?? throw new Exception("Canvas queue actor not properly created")); +}); + builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/Management.Web/Shared/InitializeNewCourse.razor b/Management.Web/Shared/InitializeNewCourse.razor index 144b227..43001ae 100644 --- a/Management.Web/Shared/InitializeNewCourse.razor +++ b/Management.Web/Shared/InitializeNewCourse.razor @@ -56,7 +56,7 @@ loadingTerms = true; terms = await canvas.GetCurrentTermsFor(); loadingTerms = false; - directoriesNotUsed = fileStorageManager.GetEmptyDirectories(); + directoriesNotUsed = await fileStorageManager.GetEmptyDirectories(); } private async Task SaveNewCourse() diff --git a/Management/Management.csproj b/Management/Management.csproj index 23d6e0b..6ae3274 100644 --- a/Management/Management.csproj +++ b/Management/Management.csproj @@ -7,6 +7,8 @@ + + @@ -16,4 +18,7 @@ + + + diff --git a/Management/Services/Actors/CanvasQueue.cs b/Management/Services/Actors/CanvasQueue.cs new file mode 100644 index 0000000..f7ca500 --- /dev/null +++ b/Management/Services/Actors/CanvasQueue.cs @@ -0,0 +1,9 @@ +using Akka.Actor; + +using Management.Services.Canvas; + +public class CanvasQueue(IActorRef canvasQueueActor) +{ + private readonly IActorRef canvasQueueActor = canvasQueueActor; + +} \ No newline at end of file diff --git a/Management/Services/Actors/CanvasQueueActor.cs b/Management/Services/Actors/CanvasQueueActor.cs new file mode 100644 index 0000000..7c841c0 --- /dev/null +++ b/Management/Services/Actors/CanvasQueueActor.cs @@ -0,0 +1,18 @@ +using Akka.Actor; + +using Microsoft.Extensions.DependencyInjection; + +public class CanvasQueueActor : ReceiveActor +{ + private readonly IServiceProvider serviceProvider; + private readonly IServiceScope scope; + private readonly ILogger logger; + + public CanvasQueueActor(IServiceProvider serviceProviderArg) + { + serviceProvider = serviceProviderArg; + scope = serviceProvider.CreateScope(); + logger = scope.ServiceProvider.GetRequiredService>(); + + } +} \ No newline at end of file diff --git a/Management/Services/Actors/LocalStorageActor.cs b/Management/Services/Actors/LocalStorageActor.cs new file mode 100644 index 0000000..44d840e --- /dev/null +++ b/Management/Services/Actors/LocalStorageActor.cs @@ -0,0 +1,61 @@ +using Akka.Actor; + +using LocalModels; + +using Microsoft.Extensions.DependencyInjection; + +public class LocalStorageActor : ReceiveActor +{ + private readonly IServiceProvider serviceProvider; + private readonly IServiceScope scope; + private readonly ILogger logger; + private readonly FileStorageManager storage; + + private DateTime? cacheTime { get; set; } = null; + private IEnumerable? cachedCourses { get; set; } = null; + private readonly int cacheSeconds = 2; + + public LocalStorageActor(IServiceProvider serviceProviderArg) + { + serviceProvider = serviceProviderArg; + scope = serviceProvider.CreateScope(); + logger = scope.ServiceProvider.GetRequiredService>(); + storage = scope.ServiceProvider.GetRequiredService(); + + Receive(m => + { + storage + .GetEmptyDirectories() + .PipeTo(Sender); + }); + + ReceiveAsync(async m => + { + var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds; + + if (cachedCourses != null && secondsFromLastLoad < cacheSeconds) + { + logger.LogInformation("returning cached courses from file"); + Sender.Tell(cachedCourses); + return; + } + + cachedCourses = await storage.LoadSavedCourses(); + cacheTime = DateTime.Now; + Sender.Tell(cachedCourses); + }); + + ReceiveAsync(async m => + { + cacheTime = null; + cachedCourses = null; + await storage.SaveCourseAsync(m.Course, m.PreviouslyStoredCourse); + }); + } +} + +public record EmptyDirectoryAsk(); +public record SavedCoursesAsk(); + +public record SaveCoursesRequest(LocalCourse Course, LocalCourse? PreviouslyStoredCourse); +public record SaveCoursesResponseSuccess(); \ No newline at end of file diff --git a/Management/Services/Actors/LocalStorageCache.cs b/Management/Services/Actors/LocalStorageCache.cs new file mode 100644 index 0000000..b00c64a --- /dev/null +++ b/Management/Services/Actors/LocalStorageCache.cs @@ -0,0 +1,23 @@ +using Akka.Actor; + +using LocalModels; + +public class LocalStorageCache(IActorRef storageActor) : IFileStorageManager +{ + private readonly IActorRef storageActor = storageActor; + + public async Task> GetEmptyDirectories() + { + return await storageActor.Ask>(new EmptyDirectoryAsk()); + } + + public async Task> LoadSavedCourses() + { + return await storageActor.Ask>(new SavedCoursesAsk()); + } + + public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse) + { + await storageActor.Ask(new SaveCoursesRequest(course, previouslyStoredCourse)); + } +} \ No newline at end of file diff --git a/Management/Services/AkkaService.cs b/Management/Services/AkkaService.cs new file mode 100644 index 0000000..5186f85 --- /dev/null +++ b/Management/Services/AkkaService.cs @@ -0,0 +1,60 @@ + +using Akka.Actor; +using Akka.DependencyInjection; + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace Management.Services; + + +public class AkkaService( + IServiceProvider serviceProvider, + IHostApplicationLifetime appLifetime, + IConfiguration configuration +) : IHostedService +{ + private ActorSystem? actorSystem; + private readonly IConfiguration configuration = configuration; + private readonly IServiceProvider serviceProvider = serviceProvider; + private readonly IHostApplicationLifetime applicationLifetime = appLifetime; + public IActorRef? CanvasQueueActor { get; private set; } + public IActorRef? StorageActor { get; private set; } + + public Task StartAsync(CancellationToken cancellationToken) + { + var bootstrap = BootstrapSetup.Create(); + var dependencyInjectionSetup = DependencyResolverSetup.Create(serviceProvider); + + var mergedSystemSetup = bootstrap.And(dependencyInjectionSetup); + + actorSystem = ActorSystem.Create("canavas-management-actor-system", mergedSystemSetup); + + var canvasQueueProps = DependencyResolver.For(actorSystem).Props(); + CanvasQueueActor = actorSystem.ActorOf(canvasQueueProps, "canvasQueue"); + var localStorageProps = DependencyResolver.For(actorSystem).Props(); + StorageActor = actorSystem.ActorOf(localStorageProps, "localStorage"); + + // crash if the actor system crashes, awaiting never returns... + actorSystem.WhenTerminated.ContinueWith(tr => + { + applicationLifetime.StopApplication(); + }); + + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + + // public void Tell(object message) + // { + // userSessionSupervisor?.Tell(message); + // } + + // public Task Ask(object message) + // { + // return userSessionSupervisor.Ask(message); + // } + +} diff --git a/Management/Services/Canvas/CanvasService.cs b/Management/Services/Canvas/CanvasService.cs index 3f961db..c0378c5 100644 --- a/Management/Services/Canvas/CanvasService.cs +++ b/Management/Services/Canvas/CanvasService.cs @@ -4,7 +4,6 @@ using CanvasModel.Courses; using CanvasModel.EnrollmentTerms; using CanvasModel.Modules; using CanvasModel.Pages; -using Microsoft.Extensions.Logging; using RestSharp; namespace Management.Services.Canvas; @@ -35,7 +34,7 @@ public class CanvasService( ICanvasQuizService Quizzes, ICanvasCoursePageService Pages, MyLogger logger -):ICanvasService +) : ICanvasService { private readonly IWebRequestor webRequestor = webRequestor; private readonly CanvasServiceUtils utils = utils; diff --git a/Management/Services/Files/FileStorageManager.cs b/Management/Services/Files/FileStorageManager.cs index bd48c0e..5a24958 100644 --- a/Management/Services/Files/FileStorageManager.cs +++ b/Management/Services/Files/FileStorageManager.cs @@ -1,7 +1,7 @@ using LocalModels; using Management.Services; -public class FileStorageManager : IFileStorageManager +public class FileStorageManager { private readonly MyLogger logger; private readonly CourseMarkdownLoader _courseMarkdownLoader; @@ -39,7 +39,7 @@ public class FileStorageManager : IFileStorageManager return await _courseMarkdownLoader.LoadSavedCourses(); } - public IEnumerable GetEmptyDirectories() + public async Task> GetEmptyDirectories() { if (!Directory.Exists(_basePath)) throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist"); diff --git a/Management/Services/Files/FileStorageManagerCached.cs b/Management/Services/Files/FileStorageManagerCached.cs index be4ff02..f0d2d80 100644 --- a/Management/Services/Files/FileStorageManagerCached.cs +++ b/Management/Services/Files/FileStorageManagerCached.cs @@ -1,50 +1,50 @@ -using System.Diagnostics.CodeAnalysis; -using LocalModels; +// using System.Diagnostics.CodeAnalysis; +// using LocalModels; -public class FileStorageManagerCached : IFileStorageManager -{ - private readonly FileStorageManager manager; +// public class FileStorageManagerCached : IFileStorageManager +// { +// private readonly FileStorageManager manager; - private readonly object cacheLock = new object(); // Lock object for synchronization +// private readonly object cacheLock = new object(); // Lock object for synchronization - private DateTime? cacheTime { get; set; } = null; - private IEnumerable? cachedCourses { get; set; } = null; - private ILogger logger { get; } +// private DateTime? cacheTime { get; set; } = null; +// private IEnumerable? cachedCourses { get; set; } = null; +// private ILogger logger { get; } - private readonly int cacheSeconds = 2; - public FileStorageManagerCached(FileStorageManager manager, ILogger logger) - { - this.manager = manager; - this.logger = logger; - } - public IEnumerable GetEmptyDirectories() - { - return manager.GetEmptyDirectories(); - } +// private readonly int cacheSeconds = 2; +// public FileStorageManagerCached(FileStorageManager manager, ILogger logger) +// { +// this.manager = manager; +// this.logger = logger; +// } +// public Task> GetEmptyDirectories() +// { +// return manager.GetEmptyDirectories(); +// } - public async Task> LoadSavedCourses() - { +// public async Task> LoadSavedCourses() +// { - var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds; +// var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds; - if (cachedCourses != null && secondsFromLastLoad < cacheSeconds) - { - logger.LogInformation("returning cached courses from file"); - return cachedCourses; - } +// if (cachedCourses != null && secondsFromLastLoad < cacheSeconds) +// { +// logger.LogInformation("returning cached courses from file"); +// return cachedCourses; +// } - cachedCourses = await manager.LoadSavedCourses(); - cacheTime = DateTime.Now; - return cachedCourses; - } +// cachedCourses = await manager.LoadSavedCourses(); +// cacheTime = DateTime.Now; +// return cachedCourses; +// } - public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse) - { - // race condition... - cacheTime = null; - cachedCourses = null; - await manager.SaveCourseAsync(course, previouslyStoredCourse); - } -} +// public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse) +// { +// // race condition... +// cacheTime = null; +// cachedCourses = null; +// await manager.SaveCourseAsync(course, previouslyStoredCourse); +// } +// } diff --git a/Management/Services/Files/IFileStorageManager.cs b/Management/Services/Files/IFileStorageManager.cs index dfc5c11..b45ffc5 100644 --- a/Management/Services/Files/IFileStorageManager.cs +++ b/Management/Services/Files/IFileStorageManager.cs @@ -4,5 +4,5 @@ public interface IFileStorageManager { Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse); Task> LoadSavedCourses(); - IEnumerable GetEmptyDirectories(); + Task> GetEmptyDirectories(); } diff --git a/README.md b/README.md index 6022bf0..571ea82 100644 --- a/README.md +++ b/README.md @@ -48,3 +48,5 @@ schedule planning view? just outline concepts? (maybe some non-canvas scheduled holiday schedule multi-seciton support for due dates/times + +better error handling when files are unparseable \ No newline at end of file