switched to xunit

This commit is contained in:
2024-05-03 18:49:51 -06:00
parent aab38c3e9b
commit 26bf2bbbd1
34 changed files with 643 additions and 298 deletions

View File

@@ -1,50 +1,127 @@
# Top-most EditorConfig file
root = true root = true
# use unix newline characters # All files
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
[*] [*]
insert_final_newline = true
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
trim_trailing_whitespace = true end_of_line = lf
[project.json]
indent_size = 2
# Generated code
[*{_AssemblyInfo.cs,.notsupported.cs}]
generated_code = true
# C# files # C# files
[*.cs] [*.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 # 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_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = 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_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 csharp_new_line_between_query_expression_clauses = true
# Indentation preferences # Indentation preferences
@@ -52,100 +129,8 @@ csharp_indent_block_contents = true
csharp_indent_braces = false csharp_indent_braces = false
csharp_indent_case_contents = true csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# 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
# Space preferences # Space preferences
csharp_space_after_cast = false 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_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after 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_colon_in_inheritance_clause = true
csharp_space_before_comma = false csharp_space_before_comma = false
csharp_space_before_dot = 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_parentheses = false
csharp_space_between_square_brackets = false csharp_space_between_square_brackets = false
# C++ Files # Wrapping preferences
[*.{cpp,h,in}] csharp_preserve_single_line_blocks = true
curly_bracket_next_line = true csharp_preserve_single_line_statements = true
indent_brace_style = Allman
# Xml project files #### Naming styles ####
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] [*.{cs,vb}]
indent_size = 2
[*.{csproj,vbproj,proj,nativeproj,locproj}] # Naming rules
charset = utf-8
# Xml build files dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
[*.builds] dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
indent_size = 2 dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
# Xml files dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
[*.{xml,stylecop,resx,ruleset}] dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
indent_size = 2 dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
# Xml config files dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
[*.{props,targets,config,nuspec}] dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
indent_size = 2 dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
# YAML config files dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
[*.{yml,yaml}] dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
indent_size = 2 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

View File

@@ -3,7 +3,7 @@ using CanvasModel.EnrollmentTerms;
public class DeserializationTests public class DeserializationTests
{ {
[Test] [Fact]
public void TestTerm() public void TestTerm()
{ {

View File

@@ -1,6 +1,6 @@
public class CalendarMonthTests public class CalendarMonthTests
{ {
[Test] [Fact]
public void TestCalendarMonthCanGetFirstWeek() public void TestCalendarMonthCanGetFirstWeek()
{ {
var month = new CalendarMonth(2023, 2); var month = new CalendarMonth(2023, 2);
@@ -12,7 +12,7 @@ public class CalendarMonthTests
month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek); month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek);
} }
[Test] [Fact]
public void TestCanGetAnotherMonthsFirstWeek() public void TestCanGetAnotherMonthsFirstWeek()
{ {
var month = new CalendarMonth(2023, 4); var month = new CalendarMonth(2023, 4);
@@ -24,7 +24,7 @@ public class CalendarMonthTests
month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek); month.Weeks.First().Should().BeEquivalentTo(expectedFirstWeek);
} }
[Test] [Fact]
public void TestCorrectNumberOfWeeks() public void TestCorrectNumberOfWeeks()
{ {
var month = new CalendarMonth(2023, 4); var month = new CalendarMonth(2023, 4);
@@ -32,7 +32,7 @@ public class CalendarMonthTests
month.Weeks.Count().Should().Be(6); month.Weeks.Count().Should().Be(6);
} }
[Test] [Fact]
public void TestLastWeekIsCorrect() public void TestLastWeekIsCorrect()
{ {
var month = new CalendarMonth(2023, 4); var month = new CalendarMonth(2023, 4);

View File

@@ -2,7 +2,7 @@
// public class ConfigurationTests // public class ConfigurationTests
// { // {
// [Test] // [Fact]
// public void TestCanCreateConfigFromTermAndDays() // public void TestCanCreateConfigFromTermAndDays()
// { // {
// DateTime startAt = new DateTime(2022, 1, 1); // DateTime startAt = new DateTime(2022, 1, 1);

View File

@@ -1,6 +1,6 @@
// public class ModuleTests // public class ModuleTests
// { // {
// [Test] // [Fact]
// public void CanAddModule() // public void CanAddModule()
// { // {
// var manager = new ModuleManager(); // var manager = new ModuleManager();
@@ -11,7 +11,7 @@
// manager.Modules.First().Should().Be(module); // manager.Modules.First().Should().Be(module);
// } // }
// [Test] // [Fact]
// public void CanAddAssignmentToCorrectModule() // public void CanAddAssignmentToCorrectModule()
// { // {
// var manager = new ModuleManager(); // var manager = new ModuleManager();

View File

@@ -4,7 +4,7 @@
// public class SemesterPlannerTests // public class SemesterPlannerTests
// { // {
// [Test] // [Fact]
// public void TestCanCreatePlanner() // public void TestCanCreatePlanner()
// { // {
@@ -19,7 +19,7 @@
// semester.Months.Count().Should().Be(1); // semester.Months.Count().Should().Be(1);
// } // }
// [Test] // [Fact]
// public void TestNewPlannerHasCorrectNumberOfMonths() // public void TestNewPlannerHasCorrectNumberOfMonths()
// { // {
// var config = new SemesterCalendarConfig( // var config = new SemesterCalendarConfig(
@@ -33,7 +33,7 @@
// semester.Months.Count().Should().Be(2); // semester.Months.Count().Should().Be(2);
// } // }
// [Test] // [Fact]
// public void TestNewPlannerHandlesTermsThatWrapYears() // public void TestNewPlannerHandlesTermsThatWrapYears()
// { // {
// var config = new SemesterCalendarConfig( // var config = new SemesterCalendarConfig(
@@ -47,7 +47,7 @@
// semester.Months.Count().Should().Be(2); // semester.Months.Count().Should().Be(2);
// } // }
// [Test] // [Fact]
// public void TestSemesterGetsCorrectMonths() // public void TestSemesterGetsCorrectMonths()
// { // {
// var config = new SemesterCalendarConfig( // var config = new SemesterCalendarConfig(
@@ -63,7 +63,7 @@
// } // }
// [Test] // [Fact]
// public void TestMonthsCanWrapYears() // public void TestMonthsCanWrapYears()
// { // {
// var config = new SemesterCalendarConfig( // var config = new SemesterCalendarConfig(
@@ -81,7 +81,7 @@
// semester.Months.Last().Year.Should().Be(2023); // semester.Months.Last().Year.Should().Be(2023);
// } // }
// [Test] // [Fact]
// public void TestSemesterTracksDaysOfWeek() // public void TestSemesterTracksDaysOfWeek()
// { // {
// DayOfWeek[] days = new DayOfWeek[] { DayOfWeek.Monday }; // DayOfWeek[] days = new DayOfWeek[] { DayOfWeek.Monday };

View File

@@ -9,15 +9,25 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="akka.testkit" Version="1.5.18" />
<PackageReference Include="Akka.TestKit.Xunit2" Version="1.5.18" />
<PackageReference Include="FluentAssertions" Version="6.8.0" /> <PackageReference Include="FluentAssertions" Version="6.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="nsubstitute" Version="5.1.0" /> <PackageReference Include="nsubstitute" Version="5.1.0" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" /> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" /> <PrivateAssets>all</PrivateAssets>
<PackageReference Include="coverlet.collector" Version="3.1.2" /> </PackageReference>
<PackageReference Include="RestSharp" Version="108.0.3" /> <PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -2,7 +2,7 @@ using LocalModels;
public class AssignmentMarkdownTests public class AssignmentMarkdownTests
{ {
[Test] [Fact]
public void TestCanParseAssignmentSettings() public void TestCanParseAssignmentSettings()
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
@@ -24,7 +24,7 @@ public class AssignmentMarkdownTests
var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown);
parsedAssignment.Should().BeEquivalentTo(assignment); parsedAssignment.Should().BeEquivalentTo(assignment);
} }
[Test] [Fact]
public void AssignmentWithEmptyRubric_CanBeParsed() public void AssignmentWithEmptyRubric_CanBeParsed()
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
@@ -43,7 +43,7 @@ public class AssignmentMarkdownTests
var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown);
parsedAssignment.Should().BeEquivalentTo(assignment); parsedAssignment.Should().BeEquivalentTo(assignment);
} }
[Test] [Fact]
public void AssignmentWithEmptySubmissionTypes_CanBeParsed() public void AssignmentWithEmptySubmissionTypes_CanBeParsed()
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
@@ -66,7 +66,7 @@ public class AssignmentMarkdownTests
parsedAssignment.Should().BeEquivalentTo(assignment); parsedAssignment.Should().BeEquivalentTo(assignment);
} }
[Test] [Fact]
public void AssignmentWithoutLockAtDate_CanBeParsed() public void AssignmentWithoutLockAtDate_CanBeParsed()
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
@@ -89,7 +89,7 @@ public class AssignmentMarkdownTests
parsedAssignment.Should().BeEquivalentTo(assignment); parsedAssignment.Should().BeEquivalentTo(assignment);
} }
[Test] [Fact]
public void AssignmentWithoutDescription_CanBeParsed() public void AssignmentWithoutDescription_CanBeParsed()
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
@@ -111,7 +111,7 @@ public class AssignmentMarkdownTests
var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown); var parsedAssignment = LocalAssignment.ParseMarkdown(assignmentMarkdown);
parsedAssignment.Should().BeEquivalentTo(assignment); parsedAssignment.Should().BeEquivalentTo(assignment);
} }
[Test] [Fact]
public void Assignments_CanHaveThreeDashes() public void Assignments_CanHaveThreeDashes()
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()

View File

@@ -5,14 +5,13 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute; using NSubstitute;
using NUnit.Framework.Internal;
public class FileStorageTests public class FileStorageTests
{ {
private IFileStorageManager fileManager { get; set; } private FileStorageManager fileManager { get; set; }
private static string setupTempDirectory() public FileStorageTests()
{ {
var tempDirectory = Path.GetTempPath(); var tempDirectory = Path.GetTempPath();
var storageDirectory = tempDirectory + "fileStorageTests"; var storageDirectory = tempDirectory + "fileStorageTests";
@@ -29,14 +28,6 @@ public class FileStorageTests
dir.Delete(true); dir.Delete(true);
} }
return storageDirectory;
}
[SetUp]
public void SetUp()
{
var storageDirectory = setupTempDirectory();
var fileManagerLogger = new MyLogger<FileStorageManager>(NullLogger<FileStorageManager>.Instance); var fileManagerLogger = new MyLogger<FileStorageManager>(NullLogger<FileStorageManager>.Instance);
var markdownLoaderLogger = new MyLogger<CourseMarkdownLoader>(NullLogger<CourseMarkdownLoader>.Instance); var markdownLoaderLogger = new MyLogger<CourseMarkdownLoader>(NullLogger<CourseMarkdownLoader>.Instance);
var markdownSaverLogger = new MyLogger<MarkdownCourseSaver>(NullLogger<MarkdownCourseSaver>.Instance); var markdownSaverLogger = new MyLogger<MarkdownCourseSaver>(NullLogger<MarkdownCourseSaver>.Instance);
@@ -52,7 +43,7 @@ public class FileStorageTests
fileManager = new FileStorageManager(fileManagerLogger, markdownLoader, markdownSaver, otherLogger, fileConfiguration); fileManager = new FileStorageManager(fileManagerLogger, markdownLoader, markdownSaver, otherLogger, fileConfiguration);
} }
[Test] [Fact]
public async Task EmptyCourse_CanBeSavedAndLoaded() public async Task EmptyCourse_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new LocalCourse LocalCourse testCourse = new LocalCourse
@@ -69,7 +60,7 @@ public class FileStorageTests
loadedCourse.Should().BeEquivalentTo(testCourse); loadedCourse.Should().BeEquivalentTo(testCourse);
} }
[Test] [Fact]
public async Task CourseSettings_CanBeSavedAndLoaded() public async Task CourseSettings_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() LocalCourse testCourse = new()
@@ -95,7 +86,7 @@ public class FileStorageTests
} }
[Test] [Fact]
public async Task EmptyCourseModules_CanBeSavedAndLoaded() public async Task EmptyCourseModules_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() LocalCourse testCourse = new()
@@ -119,7 +110,7 @@ public class FileStorageTests
loadedCourse.Modules.Should().BeEquivalentTo(testCourse.Modules); loadedCourse.Modules.Should().BeEquivalentTo(testCourse.Modules);
} }
[Test] [Fact]
public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded() public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() LocalCourse testCourse = new()
@@ -160,7 +151,7 @@ public class FileStorageTests
} }
[Test] [Fact]
public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded() public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() LocalCourse testCourse = new()
@@ -204,7 +195,7 @@ public class FileStorageTests
} }
[Test] [Fact]
public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData() public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData()
{ {
LocalCourse testCourse = new() LocalCourse testCourse = new()
@@ -271,7 +262,7 @@ public class FileStorageTests
} }
[Test] [Fact]
public async Task MarkdownStorage_CanPersistPages() public async Task MarkdownStorage_CanPersistPages()
{ {
LocalCourse testCourse = new() LocalCourse testCourse = new()

View File

@@ -2,7 +2,7 @@ using LocalModels;
public class PageMarkdownTests public class PageMarkdownTests
{ {
[Test] [Fact]
public void TestCanParsePage() public void TestCanParsePage()
{ {
var page = new LocalCoursePage var page = new LocalCoursePage

View File

@@ -2,7 +2,7 @@ using LocalModels;
public class MatchingTests public class MatchingTests
{ {
[Test] [Fact]
public void CanParseMatchingQuestion() public void CanParseMatchingQuestion()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -29,7 +29,7 @@ Match the following terms & definitions
firstQuestion.Answers.First().MatchedText.Should().Be("a single command to be executed"); firstQuestion.Answers.First().MatchedText.Should().Be("a single command to be executed");
} }
[Test] [Fact]
public void CanCreateMarkdownForMatchingQuesiton() public void CanCreateMarkdownForMatchingQuesiton()
{ {
var rawMarkdownQuiz = @" 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.)"; ^ keyword - reserved word that has special meaning in a program (e.g. class, void, static, etc.)";
questionMarkdown.Should().Contain(expectedMarkdown); questionMarkdown.Should().Contain(expectedMarkdown);
} }
[Test] [Fact]
public void WhitespaceIsOptional() public void WhitespaceIsOptional()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"

View File

@@ -3,7 +3,7 @@ using LocalModels;
public class MultipleAnswersTests public class MultipleAnswersTests
{ {
[Test] [Fact]
public void QuzMarkdownIncludesMultipleAnswerQuestion() public void QuzMarkdownIncludesMultipleAnswerQuestion()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -43,7 +43,7 @@ oneline question
markdown.Should().Contain(expectedQuestionString); markdown.Should().Contain(expectedQuestionString);
} }
[Test] [Fact]
public void CanParseQuestionWithMultipleAnswers() public void CanParseQuestionWithMultipleAnswers()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -81,7 +81,7 @@ Which events are triggered when the user clicks on an input field?
} }
[Test] [Fact]
public void CanUseBracesInAnswerFormultipleAnswer() public void CanUseBracesInAnswerFormultipleAnswer()
{ {
var rawMarkdownQuestion = @" var rawMarkdownQuestion = @"
@@ -95,7 +95,7 @@ Which events are triggered when the user clicks on an input field?
question.Answers.Count().Should().Be(2); question.Answers.Count().Should().Be(2);
} }
[Test] [Fact]
public void CanUseBracesInAnswerFormultipleAnswer_MultiLine() public void CanUseBracesInAnswerFormultipleAnswer_MultiLine()
{ {
var rawMarkdownQuestion = @" var rawMarkdownQuestion = @"

View File

@@ -2,7 +2,7 @@ using LocalModels;
public class MultipleChoiceTests public class MultipleChoiceTests
{ {
[Test] [Fact]
public void QuzMarkdownIncludesMultipleChoiceQuestion() public void QuzMarkdownIncludesMultipleChoiceQuestion()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -58,7 +58,7 @@ endline
} }
[Test] [Fact]
public void LetterOptionalForMultipleChoice() public void LetterOptionalForMultipleChoice()
{ {

View File

@@ -4,7 +4,7 @@ using LocalModels;
public class QuizDeterministicChecks public class QuizDeterministicChecks
{ {
[Test] [Fact]
public void SerializationIsDeterministic_EmptyQuiz() public void SerializationIsDeterministic_EmptyQuiz()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -23,7 +23,7 @@ public class QuizDeterministicChecks
parsedQuiz.Should().BeEquivalentTo(quiz); parsedQuiz.Should().BeEquivalentTo(quiz);
} }
[Test] [Fact]
public void SerializationIsDeterministic_ShowCorrectAnswers() public void SerializationIsDeterministic_ShowCorrectAnswers()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -43,7 +43,7 @@ public class QuizDeterministicChecks
parsedQuiz.Should().BeEquivalentTo(quiz); parsedQuiz.Should().BeEquivalentTo(quiz);
} }
[Test] [Fact]
public void SerializationIsDeterministic_ShortAnswer() public void SerializationIsDeterministic_ShortAnswer()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -71,7 +71,7 @@ public class QuizDeterministicChecks
parsedQuiz.Should().BeEquivalentTo(quiz); parsedQuiz.Should().BeEquivalentTo(quiz);
} }
[Test] [Fact]
public void SerializationIsDeterministic_Essay() public void SerializationIsDeterministic_Essay()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -99,7 +99,7 @@ public class QuizDeterministicChecks
parsedQuiz.Should().BeEquivalentTo(quiz); parsedQuiz.Should().BeEquivalentTo(quiz);
} }
[Test] [Fact]
public void SerializationIsDeterministic_MultipleAnswer() public void SerializationIsDeterministic_MultipleAnswer()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -138,7 +138,7 @@ public class QuizDeterministicChecks
parsedQuiz.Should().BeEquivalentTo(quiz); parsedQuiz.Should().BeEquivalentTo(quiz);
} }
[Test] [Fact]
public void SerializationIsDeterministic_MultipleChoice() public void SerializationIsDeterministic_MultipleChoice()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -176,7 +176,7 @@ public class QuizDeterministicChecks
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown); var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
parsedQuiz.Should().BeEquivalentTo(quiz); parsedQuiz.Should().BeEquivalentTo(quiz);
} }
[Test] [Fact]
public void SerializationIsDeterministic_Matching() public void SerializationIsDeterministic_Matching()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()

View File

@@ -4,7 +4,7 @@ using LocalModels;
// try to follow syntax from https://github.com/gpoore/text2qti // try to follow syntax from https://github.com/gpoore/text2qti
public class QuizMarkdownTests public class QuizMarkdownTests
{ {
[Test] [Fact]
public void CanSerializeQuizToMarkdown() public void CanSerializeQuizToMarkdown()
{ {
var quiz = new LocalQuiz() var quiz = new LocalQuiz()
@@ -37,7 +37,7 @@ this is my description in markdown
} }
[Test] [Fact]
public void TestCanParseMarkdownQuizWithNoQuestions() public void TestCanParseMarkdownQuizWithNoQuestions()
{ {
var rawMarkdownQuiz = new StringBuilder(); var rawMarkdownQuiz = new StringBuilder();
@@ -68,7 +68,7 @@ this is my description in markdown
quiz.AllowedAttempts.Should().Be(-1); quiz.AllowedAttempts.Should().Be(-1);
quiz.Description.Should().Be(expectedDescription.ToString()); quiz.Description.Should().Be(expectedDescription.ToString());
} }
[Test] [Fact]
public void TestCanParseMarkdownQuizPassword() public void TestCanParseMarkdownQuizPassword()
{ {
@@ -94,7 +94,7 @@ this is my description in markdown
quiz.Password.Should().Be(password); quiz.Password.Should().Be(password);
} }
[Test] [Fact]
public void TestCanParseMarkdownQuiz_CanConfigureToShowCorrectAnswers() public void TestCanParseMarkdownQuiz_CanConfigureToShowCorrectAnswers()
{ {
var rawMarkdownQuiz = new StringBuilder(); var rawMarkdownQuiz = new StringBuilder();
@@ -118,7 +118,7 @@ this is my description in markdown
quiz.showCorrectAnswers.Should().BeFalse(); quiz.showCorrectAnswers.Should().BeFalse();
} }
[Test] [Fact]
public void TestCanParseQuizWithQuestions() public void TestCanParseQuizWithQuestions()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -159,7 +159,7 @@ b) false
firstQuestion.Answers.ElementAt(1).Text.Should().Contain("endline"); firstQuestion.Answers.ElementAt(1).Text.Should().Contain("endline");
} }
[Test] [Fact]
public void CanParseMultipleQuestions() public void CanParseMultipleQuestions()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -192,7 +192,7 @@ b) false
secondQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_CHOICE); secondQuestion.QuestionType.Should().Be(QuestionType.MULTIPLE_CHOICE);
} }
[Test] [Fact]
public void ShortAnswerToMarkdown_IsCorrect() public void ShortAnswerToMarkdown_IsCorrect()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -220,7 +220,7 @@ Which events are triggered when the user clicks on an input field?
short_answer"; short_answer";
questionMarkdown.Should().Contain(expectedMarkdown); questionMarkdown.Should().Contain(expectedMarkdown);
} }
[Test] [Fact]
public void NegativePoints_IsAllowed() public void NegativePoints_IsAllowed()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -244,7 +244,7 @@ short answer
var firstQuestion = quiz.Questions.First(); var firstQuestion = quiz.Questions.First();
firstQuestion.Points.Should().Be(-4); firstQuestion.Points.Should().Be(-4);
} }
[Test] [Fact]
public void FloatingPointPoints_IsAllowed() public void FloatingPointPoints_IsAllowed()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"

View File

@@ -2,7 +2,7 @@ using LocalModels;
public class TextAnswerTests public class TextAnswerTests
{ {
[Test] [Fact]
public void CanParseEssay() public void CanParseEssay()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -28,7 +28,7 @@ essay
firstQuestion.Text.Should().NotContain("essay"); firstQuestion.Text.Should().NotContain("essay");
} }
[Test] [Fact]
public void CanParseShortAnswer() public void CanParseShortAnswer()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -54,7 +54,7 @@ short answer
firstQuestion.Text.Should().NotContain("short answer"); firstQuestion.Text.Should().NotContain("short answer");
} }
[Test] [Fact]
public void ShortAnswerToMarkdown_IsCorrect() public void ShortAnswerToMarkdown_IsCorrect()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"
@@ -83,7 +83,7 @@ short_answer";
questionMarkdown.Should().Contain(expectedMarkdown); questionMarkdown.Should().Contain(expectedMarkdown);
} }
[Test] [Fact]
public void EssayQuestionToMarkdown_IsCorrect() public void EssayQuestionToMarkdown_IsCorrect()
{ {
var rawMarkdownQuiz = @" var rawMarkdownQuiz = @"

View File

@@ -3,7 +3,7 @@ using LocalModels;
public class RubricMarkdownTests public class RubricMarkdownTests
{ {
[Test] [Fact]
public void TestCanParseOneItem() public void TestCanParseOneItem()
{ {
var rawRubric = @" var rawRubric = @"
@@ -17,7 +17,7 @@ public class RubricMarkdownTests
rubric.First().Points.Should().Be(2); rubric.First().Points.Should().Be(2);
} }
[Test] [Fact]
public void TestCanParseMultipleItems() public void TestCanParseMultipleItems()
{ {
var rawRubric = @" var rawRubric = @"
@@ -32,7 +32,7 @@ public class RubricMarkdownTests
rubric.ElementAt(1).Points.Should().Be(3); rubric.ElementAt(1).Points.Should().Be(3);
} }
[Test] [Fact]
public void TestCanParseSinglePoint() public void TestCanParseSinglePoint()
{ {
var rawRubric = @" var rawRubric = @"
@@ -45,7 +45,7 @@ public class RubricMarkdownTests
rubric.First().Points.Should().Be(1); rubric.First().Points.Should().Be(1);
} }
[Test] [Fact]
public void TestCanParseSingleExtraCredit_LowerCase() public void TestCanParseSingleExtraCredit_LowerCase()
{ {
var rawRubric = @" var rawRubric = @"
@@ -57,7 +57,7 @@ public class RubricMarkdownTests
rubric.First().Label.Should().Be("(extra credit) this is the task"); rubric.First().Label.Should().Be("(extra credit) this is the task");
} }
[Test] [Fact]
public void TestCanParseSingleExtraCredit_UpperCase() public void TestCanParseSingleExtraCredit_UpperCase()
{ {
var rawRubric = @" var rawRubric = @"
@@ -69,7 +69,7 @@ public class RubricMarkdownTests
rubric.First().Label.Should().Be("(Extra Credit) this is the task"); rubric.First().Label.Should().Be("(Extra Credit) this is the task");
} }
[Test] [Fact]
public void TestCanParseFloatingPointNubmers() public void TestCanParseFloatingPointNubmers()
{ {
var rawRubric = @" var rawRubric = @"
@@ -79,7 +79,7 @@ public class RubricMarkdownTests
var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric);
rubric.First().Points.Should().Be(1.5); rubric.First().Points.Should().Be(1.5);
} }
[Test] [Fact]
public void TestCanParseNegativeNubmers() public void TestCanParseNegativeNubmers()
{ {
var rawRubric = @" var rawRubric = @"
@@ -89,7 +89,7 @@ public class RubricMarkdownTests
var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric); var rubric = LocalAssignment.ParseRubricMarkdown(rawRubric);
rubric.First().Points.Should().Be(-2.0); rubric.First().Points.Should().Be(-2.0);
} }
[Test] [Fact]
public void TestCanParseNegativeFloatingPointNubmers() public void TestCanParseNegativeFloatingPointNubmers()
{ {
var rawRubric = @" var rawRubric = @"

View File

@@ -9,7 +9,7 @@
// public class ICanvasServiceTests // public class ICanvasServiceTests
// { // {
// [Test] // [Fact]
// public async Task CanReadCanvasSemesters() // public async Task CanReadCanvasSemesters()
// { // {
// var expectedTerms = new EnrollmentTermModel[] { // var expectedTerms = new EnrollmentTermModel[] {
@@ -28,7 +28,7 @@
// canvasTerms.Should().BeEquivalentTo(expectedTerms); // canvasTerms.Should().BeEquivalentTo(expectedTerms);
// } // }
// [Test] // [Fact]
// public async Task CanGetActiveTerms() // public async Task CanGetActiveTerms()
// { // {
// var expectedTerms = new EnrollmentTermModel[] { // var expectedTerms = new EnrollmentTermModel[] {

View File

@@ -1,3 +1,3 @@
global using System.Text.Json; global using System.Text.Json;
global using FluentAssertions; global using FluentAssertions;
global using NUnit.Framework; global using Xunit;

View File

@@ -2,7 +2,7 @@ using Management.Web.Pages.Course.CourseCalendar;
public class MonthDetailTests public class MonthDetailTests
{ {
[Test] [Fact]
public void TestCanGetMonthName() public void TestCanGetMonthName()
{ {
var calendarMonth = new CalendarMonth(2022, 2); var calendarMonth = new CalendarMonth(2022, 2);

View File

@@ -5,6 +5,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Akka" Version="1.5.20" />
<PackageReference Include="Akka.DependencyInjection" Version="1.5.20" />
<PackageReference Include="BlazorMonaco" Version="3.0.0" /> <PackageReference Include="BlazorMonaco" Version="3.0.0" />
<PackageReference Include="dotenv.net" Version="3.1.2" /> <PackageReference Include="dotenv.net" Version="3.1.2" />
<PackageReference Include="Markdig" Version="0.31.0" /> <PackageReference Include="Markdig" Version="0.31.0" />

View File

@@ -1,19 +1,25 @@
global using System.ComponentModel.DataAnnotations; global using System.ComponentModel.DataAnnotations;
global using System.Text.Json; global using System.Text.Json;
global using System.Text.Json.Serialization; global using System.Text.Json.Serialization;
global using CanvasModel; global using CanvasModel;
global using CanvasModel.Courses; global using CanvasModel.Courses;
global using CanvasModel.EnrollmentTerms; global using CanvasModel.EnrollmentTerms;
global using LocalModels; global using LocalModels;
global using Management.Planner; global using Management.Planner;
global using Management.Services; global using Management.Services;
global using Management.Services.Canvas; global using Management.Services.Canvas;
global using Management.Web.Shared; global using Management.Web.Shared;
global using Management.Web.Shared.Components; global using Management.Web.Shared.Components;
using dotenv.net; using dotenv.net;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.ResponseCompression;
using OpenTelemetry; using OpenTelemetry;
using OpenTelemetry.Logs; using OpenTelemetry.Logs;
using OpenTelemetry.Metrics; using OpenTelemetry.Metrics;
@@ -69,25 +75,40 @@ builder.Services.AddServerSideBlazor();
builder.Services.AddLogging(); builder.Services.AddLogging();
builder.Services.AddScoped(typeof(MyLogger<>)); builder.Services.AddSingleton(typeof(MyLogger<>));
builder.Services.AddScoped<IWebRequestor, WebRequestor>(); // stateless services
builder.Services.AddScoped<CanvasServiceUtils>(); builder.Services.AddSingleton<IWebRequestor, WebRequestor>();
builder.Services.AddScoped<ICanvasAssignmentService, CanvasAssignmentService>(); builder.Services.AddSingleton<CanvasServiceUtils>();
builder.Services.AddScoped<ICanvasCoursePageService, CanvasCoursePageService>(); builder.Services.AddSingleton<ICanvasAssignmentService, CanvasAssignmentService>();
builder.Services.AddScoped<ICanvasAssignmentGroupService, CanvasAssignmentGroupService>(); builder.Services.AddSingleton<ICanvasCoursePageService, CanvasCoursePageService>();
builder.Services.AddScoped<ICanvasQuizService, CanvasQuizService>(); builder.Services.AddSingleton<ICanvasAssignmentGroupService, CanvasAssignmentGroupService>();
builder.Services.AddScoped<ICanvasModuleService, CanvasModuleService>(); builder.Services.AddSingleton<ICanvasQuizService, CanvasQuizService>();
builder.Services.AddScoped<ICanvasService, CanvasService>(); builder.Services.AddSingleton<ICanvasModuleService, CanvasModuleService>();
builder.Services.AddSingleton<ICanvasService, CanvasService>();
builder.Services.AddScoped<MarkdownCourseSaver>(); builder.Services.AddSingleton<MarkdownCourseSaver>();
builder.Services.AddScoped<CourseMarkdownLoader>(); builder.Services.AddSingleton<CourseMarkdownLoader>();
builder.Services.AddScoped<IFileStorageManager>(sp =>
builder.Services.AddSingleton<FileStorageManager>();
// one actor system, maybe different actor for different pages?
builder.Services.AddSingleton<AkkaService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<AkkaService>());
// TODO: need to handle scoped requirements
builder.Services.AddSingleton(sp =>
{ {
var manager = ActivatorUtilities.CreateInstance<FileStorageManager>(sp); var akka = sp.GetRequiredService<AkkaService>();
var logger = sp.GetRequiredService<ILogger<FileStorageManagerCached>>(); return new CanvasQueue(akka.CanvasQueueActor ?? throw new Exception("Canvas queue actor not properly created"));
return new FileStorageManagerCached(manager, logger);
}); });
builder.Services.AddSingleton<IFileStorageManager>(sp =>
{
var akka = sp.GetRequiredService<AkkaService>();
return new LocalStorageCache(akka.StorageActor ?? throw new Exception("Canvas queue actor not properly created"));
});
builder.Services.AddScoped<CoursePlanner>(); builder.Services.AddScoped<CoursePlanner>();
builder.Services.AddScoped<AssignmentEditorContext>(); builder.Services.AddScoped<AssignmentEditorContext>();

View File

@@ -56,7 +56,7 @@
loadingTerms = true; loadingTerms = true;
terms = await canvas.GetCurrentTermsFor(); terms = await canvas.GetCurrentTermsFor();
loadingTerms = false; loadingTerms = false;
directoriesNotUsed = fileStorageManager.GetEmptyDirectories(); directoriesNotUsed = await fileStorageManager.GetEmptyDirectories();
} }
private async Task SaveNewCourse() private async Task SaveNewCourse()

View File

@@ -7,6 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Akka" Version="1.5.20" />
<PackageReference Include="Akka.DependencyInjection" Version="1.5.20" />
<PackageReference Include="Markdig" Version="0.31.0" /> <PackageReference Include="Markdig" Version="0.31.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" /> <PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="8.0.3" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Common" Version="8.0.3" />
@@ -16,4 +18,7 @@
<PackageReference Include="YamlDotNet" Version="13.1.1" /> <PackageReference Include="YamlDotNet" Version="13.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,9 @@
using Akka.Actor;
using Management.Services.Canvas;
public class CanvasQueue(IActorRef canvasQueueActor)
{
private readonly IActorRef canvasQueueActor = canvasQueueActor;
}

View File

@@ -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<CanvasQueueActor> logger;
public CanvasQueueActor(IServiceProvider serviceProviderArg)
{
serviceProvider = serviceProviderArg;
scope = serviceProvider.CreateScope();
logger = scope.ServiceProvider.GetRequiredService<ILogger<CanvasQueueActor>>();
}
}

View File

@@ -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<CanvasQueueActor> logger;
private readonly FileStorageManager storage;
private DateTime? cacheTime { get; set; } = null;
private IEnumerable<LocalCourse>? cachedCourses { get; set; } = null;
private readonly int cacheSeconds = 2;
public LocalStorageActor(IServiceProvider serviceProviderArg)
{
serviceProvider = serviceProviderArg;
scope = serviceProvider.CreateScope();
logger = scope.ServiceProvider.GetRequiredService<ILogger<CanvasQueueActor>>();
storage = scope.ServiceProvider.GetRequiredService<FileStorageManager>();
Receive<EmptyDirectoryAsk>(m =>
{
storage
.GetEmptyDirectories()
.PipeTo(Sender);
});
ReceiveAsync<SavedCoursesAsk>(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<SaveCoursesRequest>(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();

View File

@@ -0,0 +1,23 @@
using Akka.Actor;
using LocalModels;
public class LocalStorageCache(IActorRef storageActor) : IFileStorageManager
{
private readonly IActorRef storageActor = storageActor;
public async Task<IEnumerable<string>> GetEmptyDirectories()
{
return await storageActor.Ask<IEnumerable<string>>(new EmptyDirectoryAsk());
}
public async Task<IEnumerable<LocalCourse>> LoadSavedCourses()
{
return await storageActor.Ask<IEnumerable<LocalCourse>>(new SavedCoursesAsk());
}
public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse)
{
await storageActor.Ask<SaveCoursesResponseSuccess>(new SaveCoursesRequest(course, previouslyStoredCourse));
}
}

View File

@@ -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>();
CanvasQueueActor = actorSystem.ActorOf(canvasQueueProps, "canvasQueue");
var localStorageProps = DependencyResolver.For(actorSystem).Props<LocalStorageActor>();
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<T> Ask<T>(object message)
// {
// return userSessionSupervisor.Ask<T>(message);
// }
}

View File

@@ -4,7 +4,6 @@ using CanvasModel.Courses;
using CanvasModel.EnrollmentTerms; using CanvasModel.EnrollmentTerms;
using CanvasModel.Modules; using CanvasModel.Modules;
using CanvasModel.Pages; using CanvasModel.Pages;
using Microsoft.Extensions.Logging;
using RestSharp; using RestSharp;
namespace Management.Services.Canvas; namespace Management.Services.Canvas;
@@ -35,7 +34,7 @@ public class CanvasService(
ICanvasQuizService Quizzes, ICanvasQuizService Quizzes,
ICanvasCoursePageService Pages, ICanvasCoursePageService Pages,
MyLogger<ICanvasService> logger MyLogger<ICanvasService> logger
):ICanvasService ) : ICanvasService
{ {
private readonly IWebRequestor webRequestor = webRequestor; private readonly IWebRequestor webRequestor = webRequestor;
private readonly CanvasServiceUtils utils = utils; private readonly CanvasServiceUtils utils = utils;

View File

@@ -1,7 +1,7 @@
using LocalModels; using LocalModels;
using Management.Services; using Management.Services;
public class FileStorageManager : IFileStorageManager public class FileStorageManager
{ {
private readonly MyLogger<FileStorageManager> logger; private readonly MyLogger<FileStorageManager> logger;
private readonly CourseMarkdownLoader _courseMarkdownLoader; private readonly CourseMarkdownLoader _courseMarkdownLoader;
@@ -39,7 +39,7 @@ public class FileStorageManager : IFileStorageManager
return await _courseMarkdownLoader.LoadSavedCourses(); return await _courseMarkdownLoader.LoadSavedCourses();
} }
public IEnumerable<string> GetEmptyDirectories() public async Task<IEnumerable<string>> GetEmptyDirectories()
{ {
if (!Directory.Exists(_basePath)) if (!Directory.Exists(_basePath))
throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist"); throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist");

View File

@@ -1,50 +1,50 @@
using System.Diagnostics.CodeAnalysis; // using System.Diagnostics.CodeAnalysis;
using LocalModels; // using LocalModels;
public class FileStorageManagerCached : IFileStorageManager // public class FileStorageManagerCached : IFileStorageManager
{ // {
private readonly FileStorageManager manager; // 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 DateTime? cacheTime { get; set; } = null;
private IEnumerable<LocalCourse>? cachedCourses { get; set; } = null; // private IEnumerable<LocalCourse>? cachedCourses { get; set; } = null;
private ILogger<FileStorageManagerCached> logger { get; } // private ILogger<FileStorageManagerCached> logger { get; }
private readonly int cacheSeconds = 2; // private readonly int cacheSeconds = 2;
public FileStorageManagerCached(FileStorageManager manager, ILogger<FileStorageManagerCached> logger) // public FileStorageManagerCached(FileStorageManager manager, ILogger<FileStorageManagerCached> logger)
{ // {
this.manager = manager; // this.manager = manager;
this.logger = logger; // this.logger = logger;
} // }
public IEnumerable<string> GetEmptyDirectories() // public Task<IEnumerable<string>> GetEmptyDirectories()
{ // {
return manager.GetEmptyDirectories(); // return manager.GetEmptyDirectories();
} // }
public async Task<IEnumerable<LocalCourse>> LoadSavedCourses() // public async Task<IEnumerable<LocalCourse>> LoadSavedCourses()
{ // {
var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds; // var secondsFromLastLoad = (DateTime.Now - cacheTime)?.Seconds;
if (cachedCourses != null && secondsFromLastLoad < cacheSeconds) // if (cachedCourses != null && secondsFromLastLoad < cacheSeconds)
{ // {
logger.LogInformation("returning cached courses from file"); // logger.LogInformation("returning cached courses from file");
return cachedCourses; // return cachedCourses;
} // }
cachedCourses = await manager.LoadSavedCourses(); // cachedCourses = await manager.LoadSavedCourses();
cacheTime = DateTime.Now; // cacheTime = DateTime.Now;
return cachedCourses; // return cachedCourses;
} // }
public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse) // public async Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse)
{ // {
// race condition... // // race condition...
cacheTime = null; // cacheTime = null;
cachedCourses = null; // cachedCourses = null;
await manager.SaveCourseAsync(course, previouslyStoredCourse); // await manager.SaveCourseAsync(course, previouslyStoredCourse);
} // }
} // }

View File

@@ -4,5 +4,5 @@ public interface IFileStorageManager
{ {
Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse); Task SaveCourseAsync(LocalCourse course, LocalCourse? previouslyStoredCourse);
Task<IEnumerable<LocalCourse>> LoadSavedCourses(); Task<IEnumerable<LocalCourse>> LoadSavedCourses();
IEnumerable<string> GetEmptyDirectories(); Task<IEnumerable<string>> GetEmptyDirectories();
} }

View File

@@ -48,3 +48,5 @@ schedule planning view? just outline concepts? (maybe some non-canvas scheduled
holiday schedule holiday schedule
multi-seciton support for due dates/times multi-seciton support for due dates/times
better error handling when files are unparseable