mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
hooks
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
# Top-most EditorConfig file
|
# Top-most EditorConfig file
|
||||||
root = true
|
root = true
|
||||||
|
|
||||||
|
# use unix newline characters
|
||||||
|
end_of_line = lf
|
||||||
|
|
||||||
[*.cs]
|
[*.cs]
|
||||||
dotnet_naming_rule.methods_must_be_camel_case.severity = warning
|
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.symbols = private_methods
|
||||||
dotnet_naming_rule.methods_must_be_camel_case.style = camel_case_style
|
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_kinds = method
|
||||||
dotnet_naming_symbols.private_methods.applicable_accessibilities = private
|
dotnet_naming_symbols.private_methods.applicable_accessibilities = private
|
||||||
|
|
||||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
||||||
dotnet_diagnostic.CA2254.severity = none
|
dotnet_diagnostic.CA2254.severity = none
|
||||||
@@ -199,5 +202,3 @@ indent_size = 2
|
|||||||
# Shell scripts
|
# Shell scripts
|
||||||
[*.sh]
|
[*.sh]
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
[*.{cmd,bat}]
|
|
||||||
end_of_line = crlf
|
|
||||||
219
Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs
Normal file
219
Management.Test/Markdown/Quiz/QuizDeterministicChecks.cs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
using System.Text;
|
||||||
|
using LocalModels;
|
||||||
|
|
||||||
|
public class QuizDeterministicChecks
|
||||||
|
{
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializationIsDeterministic_EmptyQuiz()
|
||||||
|
{
|
||||||
|
var quiz = new LocalQuiz()
|
||||||
|
{
|
||||||
|
Name = "Test Quiz",
|
||||||
|
Description = "quiz description",
|
||||||
|
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
ShuffleAnswers = true,
|
||||||
|
OneQuestionAtATime = true,
|
||||||
|
LocalAssignmentGroupName = "Assignments"
|
||||||
|
};
|
||||||
|
var quizMarkdown = quiz.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
||||||
|
parsedQuiz.Should().BeEquivalentTo(quiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializationIsDeterministic_ShowCorrectAnswers()
|
||||||
|
{
|
||||||
|
var quiz = new LocalQuiz()
|
||||||
|
{
|
||||||
|
Name = "Test Quiz",
|
||||||
|
Description = "quiz description",
|
||||||
|
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
showCorrectAnswers = false,
|
||||||
|
ShuffleAnswers = true,
|
||||||
|
OneQuestionAtATime = true,
|
||||||
|
LocalAssignmentGroupName = "Assignments"
|
||||||
|
};
|
||||||
|
var quizMarkdown = quiz.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
||||||
|
parsedQuiz.Should().BeEquivalentTo(quiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializationIsDeterministic_ShortAnswer()
|
||||||
|
{
|
||||||
|
var quiz = new LocalQuiz()
|
||||||
|
{
|
||||||
|
Name = "Test Quiz",
|
||||||
|
Description = "quiz description",
|
||||||
|
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
ShuffleAnswers = true,
|
||||||
|
OneQuestionAtATime = true,
|
||||||
|
LocalAssignmentGroupName = "Assignments",
|
||||||
|
Questions = new LocalQuizQuestion[]
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Text = "test short answer",
|
||||||
|
QuestionType = QuestionType.SHORT_ANSWER,
|
||||||
|
Points = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var quizMarkdown = quiz.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
||||||
|
parsedQuiz.Should().BeEquivalentTo(quiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializationIsDeterministic_Essay()
|
||||||
|
{
|
||||||
|
var quiz = new LocalQuiz()
|
||||||
|
{
|
||||||
|
Name = "Test Quiz",
|
||||||
|
Description = "quiz description",
|
||||||
|
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
ShuffleAnswers = true,
|
||||||
|
OneQuestionAtATime = true,
|
||||||
|
LocalAssignmentGroupName = "Assignments",
|
||||||
|
Questions = new LocalQuizQuestion[]
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Text = "test essay",
|
||||||
|
QuestionType = QuestionType.ESSAY,
|
||||||
|
Points = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var quizMarkdown = quiz.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
||||||
|
parsedQuiz.Should().BeEquivalentTo(quiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializationIsDeterministic_MultipleAnswer()
|
||||||
|
{
|
||||||
|
var quiz = new LocalQuiz()
|
||||||
|
{
|
||||||
|
Name = "Test Quiz",
|
||||||
|
Description = "quiz description",
|
||||||
|
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
ShuffleAnswers = true,
|
||||||
|
OneQuestionAtATime = true,
|
||||||
|
LocalAssignmentGroupName = "Assignments",
|
||||||
|
Questions = new LocalQuizQuestion[]
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Text = "test multiple answer",
|
||||||
|
QuestionType = QuestionType.MULTIPLE_ANSWERS,
|
||||||
|
Points = 1,
|
||||||
|
Answers = new LocalQuizQuestionAnswer[]
|
||||||
|
{
|
||||||
|
new() {
|
||||||
|
Correct = true,
|
||||||
|
Text="yes",
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
Correct = true,
|
||||||
|
Text="no",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var quizMarkdown = quiz.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
||||||
|
parsedQuiz.Should().BeEquivalentTo(quiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void SerializationIsDeterministic_MultipleChoice()
|
||||||
|
{
|
||||||
|
var quiz = new LocalQuiz()
|
||||||
|
{
|
||||||
|
Name = "Test Quiz",
|
||||||
|
Description = "quiz description",
|
||||||
|
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
ShuffleAnswers = true,
|
||||||
|
OneQuestionAtATime = true,
|
||||||
|
LocalAssignmentGroupName = "Assignments",
|
||||||
|
Questions = new LocalQuizQuestion[]
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Text = "test multiple choice",
|
||||||
|
QuestionType = QuestionType.MULTIPLE_CHOICE,
|
||||||
|
Points = 1,
|
||||||
|
Answers = new LocalQuizQuestionAnswer[]
|
||||||
|
{
|
||||||
|
new() {
|
||||||
|
Correct = true,
|
||||||
|
Text="yes",
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
Correct = false,
|
||||||
|
Text="no",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var quizMarkdown = quiz.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
||||||
|
parsedQuiz.Should().BeEquivalentTo(quiz);
|
||||||
|
}
|
||||||
|
[Test]
|
||||||
|
public void SerializationIsDeterministic_Matching()
|
||||||
|
{
|
||||||
|
var quiz = new LocalQuiz()
|
||||||
|
{
|
||||||
|
Name = "Test Quiz",
|
||||||
|
Description = "quiz description",
|
||||||
|
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||||
|
ShuffleAnswers = true,
|
||||||
|
OneQuestionAtATime = true,
|
||||||
|
LocalAssignmentGroupName = "Assignments",
|
||||||
|
Questions = new LocalQuizQuestion[]
|
||||||
|
{
|
||||||
|
new ()
|
||||||
|
{
|
||||||
|
Text = "test matching",
|
||||||
|
QuestionType = QuestionType.MATCHING,
|
||||||
|
Points = 1,
|
||||||
|
Answers = new LocalQuizQuestionAnswer[]
|
||||||
|
{
|
||||||
|
new() {
|
||||||
|
Correct = true,
|
||||||
|
Text="yes",
|
||||||
|
MatchedText = "testing yes"
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
Correct = true,
|
||||||
|
Text="no",
|
||||||
|
MatchedText = "testing no"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var quizMarkdown = quiz.ToMarkdown();
|
||||||
|
|
||||||
|
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
||||||
|
parsedQuiz.Should().BeEquivalentTo(quiz);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Text;
|
||||||
using LocalModels;
|
using LocalModels;
|
||||||
|
|
||||||
// try to follow syntax from https://github.com/gpoore/text2qti
|
// try to follow syntax from https://github.com/gpoore/text2qti
|
||||||
@@ -22,7 +23,7 @@ this is my description in markdown
|
|||||||
OneQuestionAtATime = false,
|
OneQuestionAtATime = false,
|
||||||
LocalAssignmentGroupName = "someId",
|
LocalAssignmentGroupName = "someId",
|
||||||
AllowedAttempts = -1,
|
AllowedAttempts = -1,
|
||||||
Questions = new LocalQuizQuestion[] { }
|
Questions = []
|
||||||
};
|
};
|
||||||
|
|
||||||
var markdown = quiz.ToMarkdown();
|
var markdown = quiz.ToMarkdown();
|
||||||
@@ -39,28 +40,82 @@ this is my description in markdown
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCanParseMarkdownQuizWithNoQuestions()
|
public void TestCanParseMarkdownQuizWithNoQuestions()
|
||||||
{
|
{
|
||||||
var rawMarkdownQuiz = @"
|
var rawMarkdownQuiz = new StringBuilder();
|
||||||
Name: Test Quiz
|
rawMarkdownQuiz.Append("Name: Test Quiz\n");
|
||||||
ShuffleAnswers: true
|
rawMarkdownQuiz.Append("ShuffleAnswers: true\n");
|
||||||
OneQuestionAtATime: false
|
rawMarkdownQuiz.Append("OneQuestionAtATime: false\n");
|
||||||
DueAt: 2023-08-21T23:59:00
|
rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n");
|
||||||
LockAt: 2023-08-21T23:59:00
|
rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n");
|
||||||
AssignmentGroup: Assignments
|
rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n");
|
||||||
AllowedAttempts: -1
|
rawMarkdownQuiz.Append("AllowedAttempts: -1\n");
|
||||||
Description: this is the
|
rawMarkdownQuiz.Append("Description: this is the\n");
|
||||||
multi line
|
rawMarkdownQuiz.Append("multi line\n");
|
||||||
description
|
rawMarkdownQuiz.Append("description\n");
|
||||||
---
|
rawMarkdownQuiz.Append("---\n");
|
||||||
";
|
rawMarkdownQuiz.Append('\n');
|
||||||
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
|
|
||||||
|
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString());
|
||||||
|
|
||||||
|
|
||||||
|
var expectedDescription = new StringBuilder();
|
||||||
|
expectedDescription.Append("this is the\n");
|
||||||
|
expectedDescription.Append("multi line\n");
|
||||||
|
expectedDescription.Append("description");
|
||||||
|
|
||||||
quiz.Name.Should().Be("Test Quiz");
|
quiz.Name.Should().Be("Test Quiz");
|
||||||
quiz.ShuffleAnswers.Should().Be(true);
|
quiz.ShuffleAnswers.Should().Be(true);
|
||||||
quiz.OneQuestionAtATime.Should().BeFalse();
|
quiz.OneQuestionAtATime.Should().BeFalse();
|
||||||
quiz.AllowedAttempts.Should().Be(-1);
|
quiz.AllowedAttempts.Should().Be(-1);
|
||||||
quiz.Description.Should().Be(@"this is the
|
quiz.Description.Should().Be(expectedDescription.ToString());
|
||||||
multi line
|
}
|
||||||
description");
|
[Test]
|
||||||
|
public void TestCanParseMarkdownQuizPassword()
|
||||||
|
{
|
||||||
|
|
||||||
|
var password = "this-is-the-password";
|
||||||
|
var rawMarkdownQuiz = new StringBuilder();
|
||||||
|
rawMarkdownQuiz.Append("Name: Test Quiz\n");
|
||||||
|
rawMarkdownQuiz.Append($"Password: {password}\n");
|
||||||
|
rawMarkdownQuiz.Append("ShuffleAnswers: true\n");
|
||||||
|
rawMarkdownQuiz.Append("OneQuestionAtATime: false\n");
|
||||||
|
rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n");
|
||||||
|
rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n");
|
||||||
|
rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n");
|
||||||
|
rawMarkdownQuiz.Append("AllowedAttempts: -1\n");
|
||||||
|
rawMarkdownQuiz.Append("Description: this is the\n");
|
||||||
|
rawMarkdownQuiz.Append("multi line\n");
|
||||||
|
rawMarkdownQuiz.Append("description\n");
|
||||||
|
rawMarkdownQuiz.Append("---\n");
|
||||||
|
rawMarkdownQuiz.Append('\n');
|
||||||
|
|
||||||
|
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString());
|
||||||
|
|
||||||
|
|
||||||
|
quiz.Password.Should().Be(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCanParseMarkdownQuiz_CanConfigureToShowCorrectAnswers()
|
||||||
|
{
|
||||||
|
var rawMarkdownQuiz = new StringBuilder();
|
||||||
|
rawMarkdownQuiz.Append("Name: Test Quiz\n");
|
||||||
|
rawMarkdownQuiz.Append("ShuffleAnswers: true\n");
|
||||||
|
rawMarkdownQuiz.Append("OneQuestionAtATime: false\n");
|
||||||
|
rawMarkdownQuiz.Append("ShowCorrectAnswers: false\n");
|
||||||
|
rawMarkdownQuiz.Append("DueAt: 2023-08-21T23:59:00\n");
|
||||||
|
rawMarkdownQuiz.Append("LockAt: 2023-08-21T23:59:00\n");
|
||||||
|
rawMarkdownQuiz.Append("AssignmentGroup: Assignments\n");
|
||||||
|
rawMarkdownQuiz.Append("AllowedAttempts: -1\n");
|
||||||
|
rawMarkdownQuiz.Append("Description: this is the\n");
|
||||||
|
rawMarkdownQuiz.Append("multi line\n");
|
||||||
|
rawMarkdownQuiz.Append("description\n");
|
||||||
|
rawMarkdownQuiz.Append("---\n");
|
||||||
|
rawMarkdownQuiz.Append('\n');
|
||||||
|
|
||||||
|
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz.ToString());
|
||||||
|
|
||||||
|
|
||||||
|
quiz.showCorrectAnswers.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -74,14 +129,14 @@ DueAt: 2023-08-21T23:59:00
|
|||||||
LockAt: 2023-08-21T23:59:00
|
LockAt: 2023-08-21T23:59:00
|
||||||
AssignmentGroup: Assignments
|
AssignmentGroup: Assignments
|
||||||
AllowedAttempts: -1
|
AllowedAttempts: -1
|
||||||
Description: this is the
|
Description: this is the
|
||||||
multi line
|
multi line
|
||||||
description
|
description
|
||||||
---
|
---
|
||||||
Points: 2
|
Points: 2
|
||||||
`some type` of question
|
`some type` of question
|
||||||
|
|
||||||
with many
|
with many
|
||||||
|
|
||||||
```
|
```
|
||||||
lines
|
lines
|
||||||
@@ -89,7 +144,7 @@ lines
|
|||||||
|
|
||||||
*a) true
|
*a) true
|
||||||
b) false
|
b) false
|
||||||
|
|
||||||
endline";
|
endline";
|
||||||
|
|
||||||
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
|
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
|
||||||
@@ -115,7 +170,7 @@ DueAt: 2023-08-21T23:59:00
|
|||||||
LockAt: 2023-08-21T23:59:00
|
LockAt: 2023-08-21T23:59:00
|
||||||
AssignmentGroup: Assignments
|
AssignmentGroup: Assignments
|
||||||
AllowedAttempts: -1
|
AllowedAttempts: -1
|
||||||
Description: this is the
|
Description: this is the
|
||||||
multi line
|
multi line
|
||||||
description
|
description
|
||||||
---
|
---
|
||||||
@@ -148,7 +203,7 @@ DueAt: 2023-08-21T23:59:00
|
|||||||
LockAt: 2023-08-21T23:59:00
|
LockAt: 2023-08-21T23:59:00
|
||||||
AssignmentGroup: Assignments
|
AssignmentGroup: Assignments
|
||||||
AllowedAttempts: -1
|
AllowedAttempts: -1
|
||||||
Description: this is the
|
Description: this is the
|
||||||
multi line
|
multi line
|
||||||
description
|
description
|
||||||
---
|
---
|
||||||
@@ -165,196 +220,4 @@ 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]
|
|
||||||
public void SerializationIsDeterministic_EmptyQuiz()
|
|
||||||
{
|
|
||||||
var quiz = new LocalQuiz()
|
|
||||||
{
|
|
||||||
Name = "Test Quiz",
|
|
||||||
Description = "quiz description",
|
|
||||||
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
ShuffleAnswers = true,
|
|
||||||
OneQuestionAtATime = true,
|
|
||||||
LocalAssignmentGroupName = "Assignments"
|
|
||||||
};
|
|
||||||
var quizMarkdown = quiz.ToMarkdown();
|
|
||||||
|
|
||||||
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
|
||||||
parsedQuiz.Should().BeEquivalentTo(quiz);
|
|
||||||
}
|
|
||||||
[Test]
|
|
||||||
public void SerializationIsDeterministic_ShortAnswer()
|
|
||||||
{
|
|
||||||
var quiz = new LocalQuiz()
|
|
||||||
{
|
|
||||||
Name = "Test Quiz",
|
|
||||||
Description = "quiz description",
|
|
||||||
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
ShuffleAnswers = true,
|
|
||||||
OneQuestionAtATime = true,
|
|
||||||
LocalAssignmentGroupName = "Assignments",
|
|
||||||
Questions = new LocalQuizQuestion[]
|
|
||||||
{
|
|
||||||
new ()
|
|
||||||
{
|
|
||||||
Text = "test short answer",
|
|
||||||
QuestionType = QuestionType.SHORT_ANSWER,
|
|
||||||
Points = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var quizMarkdown = quiz.ToMarkdown();
|
|
||||||
|
|
||||||
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
|
||||||
parsedQuiz.Should().BeEquivalentTo(quiz);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void SerializationIsDeterministic_Essay()
|
|
||||||
{
|
|
||||||
var quiz = new LocalQuiz()
|
|
||||||
{
|
|
||||||
Name = "Test Quiz",
|
|
||||||
Description = "quiz description",
|
|
||||||
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
ShuffleAnswers = true,
|
|
||||||
OneQuestionAtATime = true,
|
|
||||||
LocalAssignmentGroupName = "Assignments",
|
|
||||||
Questions = new LocalQuizQuestion[]
|
|
||||||
{
|
|
||||||
new ()
|
|
||||||
{
|
|
||||||
Text = "test essay",
|
|
||||||
QuestionType = QuestionType.ESSAY,
|
|
||||||
Points = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var quizMarkdown = quiz.ToMarkdown();
|
|
||||||
|
|
||||||
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
|
||||||
parsedQuiz.Should().BeEquivalentTo(quiz);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void SerializationIsDeterministic_MultipleAnswer()
|
|
||||||
{
|
|
||||||
var quiz = new LocalQuiz()
|
|
||||||
{
|
|
||||||
Name = "Test Quiz",
|
|
||||||
Description = "quiz description",
|
|
||||||
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
ShuffleAnswers = true,
|
|
||||||
OneQuestionAtATime = true,
|
|
||||||
LocalAssignmentGroupName = "Assignments",
|
|
||||||
Questions = new LocalQuizQuestion[]
|
|
||||||
{
|
|
||||||
new ()
|
|
||||||
{
|
|
||||||
Text = "test multiple answer",
|
|
||||||
QuestionType = QuestionType.MULTIPLE_ANSWERS,
|
|
||||||
Points = 1,
|
|
||||||
Answers = new LocalQuizQuestionAnswer[]
|
|
||||||
{
|
|
||||||
new() {
|
|
||||||
Correct = true,
|
|
||||||
Text="yes",
|
|
||||||
},
|
|
||||||
new() {
|
|
||||||
Correct = true,
|
|
||||||
Text="no",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var quizMarkdown = quiz.ToMarkdown();
|
|
||||||
|
|
||||||
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
|
||||||
parsedQuiz.Should().BeEquivalentTo(quiz);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void SerializationIsDeterministic_MultipleChoice()
|
|
||||||
{
|
|
||||||
var quiz = new LocalQuiz()
|
|
||||||
{
|
|
||||||
Name = "Test Quiz",
|
|
||||||
Description = "quiz description",
|
|
||||||
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
ShuffleAnswers = true,
|
|
||||||
OneQuestionAtATime = true,
|
|
||||||
LocalAssignmentGroupName = "Assignments",
|
|
||||||
Questions = new LocalQuizQuestion[]
|
|
||||||
{
|
|
||||||
new ()
|
|
||||||
{
|
|
||||||
Text = "test multiple choice",
|
|
||||||
QuestionType = QuestionType.MULTIPLE_CHOICE,
|
|
||||||
Points = 1,
|
|
||||||
Answers = new LocalQuizQuestionAnswer[]
|
|
||||||
{
|
|
||||||
new() {
|
|
||||||
Correct = true,
|
|
||||||
Text="yes",
|
|
||||||
},
|
|
||||||
new() {
|
|
||||||
Correct = false,
|
|
||||||
Text="no",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var quizMarkdown = quiz.ToMarkdown();
|
|
||||||
|
|
||||||
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
|
||||||
parsedQuiz.Should().BeEquivalentTo(quiz);
|
|
||||||
}
|
|
||||||
[Test]
|
|
||||||
public void SerializationIsDeterministic_Matching()
|
|
||||||
{
|
|
||||||
var quiz = new LocalQuiz()
|
|
||||||
{
|
|
||||||
Name = "Test Quiz",
|
|
||||||
Description = "quiz description",
|
|
||||||
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
DueAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
|
||||||
ShuffleAnswers = true,
|
|
||||||
OneQuestionAtATime = true,
|
|
||||||
LocalAssignmentGroupName = "Assignments",
|
|
||||||
Questions = new LocalQuizQuestion[]
|
|
||||||
{
|
|
||||||
new ()
|
|
||||||
{
|
|
||||||
Text = "test matching",
|
|
||||||
QuestionType = QuestionType.MATCHING,
|
|
||||||
Points = 1,
|
|
||||||
Answers = new LocalQuizQuestionAnswer[]
|
|
||||||
{
|
|
||||||
new() {
|
|
||||||
Correct = true,
|
|
||||||
Text="yes",
|
|
||||||
MatchedText = "testing yes"
|
|
||||||
},
|
|
||||||
new() {
|
|
||||||
Correct = true,
|
|
||||||
Text="no",
|
|
||||||
MatchedText = "testing no"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var quizMarkdown = quiz.ToMarkdown();
|
|
||||||
|
|
||||||
var parsedQuiz = LocalQuiz.ParseMarkdown(quizMarkdown);
|
|
||||||
parsedQuiz.Should().BeEquivalentTo(quiz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,11 +5,14 @@ namespace LocalModels;
|
|||||||
|
|
||||||
public record LocalQuiz: IModuleItem
|
public record LocalQuiz: IModuleItem
|
||||||
{
|
{
|
||||||
|
|
||||||
public required string Name { get; init; }
|
public required string Name { get; init; }
|
||||||
public required string Description { get; init; }
|
public required string Description { get; init; }
|
||||||
|
public string? Password { get; init; } = null;
|
||||||
public DateTime? LockAt { get; init; }
|
public DateTime? LockAt { get; init; }
|
||||||
public DateTime DueAt { get; init; }
|
public DateTime DueAt { get; init; }
|
||||||
public bool ShuffleAnswers { get; init; } = true;
|
public bool ShuffleAnswers { get; init; } = true;
|
||||||
|
public bool showCorrectAnswers { get; init; } = true;
|
||||||
public bool OneQuestionAtATime { get; init; } = false;
|
public bool OneQuestionAtATime { get; init; } = false;
|
||||||
public string? LocalAssignmentGroupName { get; init; }
|
public string? LocalAssignmentGroupName { get; init; }
|
||||||
public int AllowedAttempts { get; init; } = -1; // -1 is infinite
|
public int AllowedAttempts { get; init; } = -1; // -1 is infinite
|
||||||
@@ -21,6 +24,7 @@ public record LocalQuiz: IModuleItem
|
|||||||
// If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null.
|
// If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null.
|
||||||
public IEnumerable<LocalQuizQuestion> Questions { get; init; } =
|
public IEnumerable<LocalQuizQuestion> Questions { get; init; } =
|
||||||
Enumerable.Empty<LocalQuizQuestion>();
|
Enumerable.Empty<LocalQuizQuestion>();
|
||||||
|
|
||||||
public ulong? GetCanvasAssignmentGroupId(IEnumerable<LocalAssignmentGroup> assignmentGroups) =>
|
public ulong? GetCanvasAssignmentGroupId(IEnumerable<LocalAssignmentGroup> assignmentGroups) =>
|
||||||
assignmentGroups
|
assignmentGroups
|
||||||
.FirstOrDefault(g => g.Name == LocalAssignmentGroupName)?
|
.FirstOrDefault(g => g.Name == LocalAssignmentGroupName)?
|
||||||
@@ -44,7 +48,9 @@ public record LocalQuiz: IModuleItem
|
|||||||
return $@"Name: {Name}
|
return $@"Name: {Name}
|
||||||
LockAt: {LockAt}
|
LockAt: {LockAt}
|
||||||
DueAt: {DueAt}
|
DueAt: {DueAt}
|
||||||
|
Password: {Password}
|
||||||
ShuffleAnswers: {ShuffleAnswers.ToString().ToLower()}
|
ShuffleAnswers: {ShuffleAnswers.ToString().ToLower()}
|
||||||
|
ShowCorrectAnswers: {showCorrectAnswers.ToString().ToLower()}
|
||||||
OneQuestionAtATime: {OneQuestionAtATime.ToString().ToLower()}
|
OneQuestionAtATime: {OneQuestionAtATime.ToString().ToLower()}
|
||||||
AssignmentGroup: {LocalAssignmentGroupName}
|
AssignmentGroup: {LocalAssignmentGroupName}
|
||||||
AllowedAttempts: {AllowedAttempts}
|
AllowedAttempts: {AllowedAttempts}
|
||||||
@@ -82,6 +88,18 @@ Description: {Description}
|
|||||||
: throw new QuizMarkdownParseException($"Error with ShuffleAnswers: {rawShuffleAnswers}");
|
: throw new QuizMarkdownParseException($"Error with ShuffleAnswers: {rawShuffleAnswers}");
|
||||||
|
|
||||||
|
|
||||||
|
var rawPassword = extractLabelValue(settings, "Password");
|
||||||
|
var password = rawPassword == null || rawPassword.Trim() == string.Empty
|
||||||
|
? null
|
||||||
|
: rawPassword;
|
||||||
|
|
||||||
|
|
||||||
|
var rawShowCorrectAnswers = extractLabelValue(settings, "ShowCorrectAnswers");
|
||||||
|
var showCorrectAnswers = bool.TryParse(rawShowCorrectAnswers, out bool parsedShowCorrectAnswers)
|
||||||
|
? parsedShowCorrectAnswers
|
||||||
|
: true; //default to true
|
||||||
|
|
||||||
|
|
||||||
var rawOneQuestionAtATime = extractLabelValue(settings, "OneQuestionAtATime");
|
var rawOneQuestionAtATime = extractLabelValue(settings, "OneQuestionAtATime");
|
||||||
var oneQuestionAtATime = bool.TryParse(rawOneQuestionAtATime, out bool parsedOneQuestion)
|
var oneQuestionAtATime = bool.TryParse(rawOneQuestionAtATime, out bool parsedOneQuestion)
|
||||||
? parsedOneQuestion
|
? parsedOneQuestion
|
||||||
@@ -112,9 +130,11 @@ Description: {Description}
|
|||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
Description = description,
|
Description = description,
|
||||||
|
Password = password,
|
||||||
LockAt = lockAt,
|
LockAt = lockAt,
|
||||||
DueAt = dueAt,
|
DueAt = dueAt,
|
||||||
ShuffleAnswers = shuffleAnswers,
|
ShuffleAnswers = shuffleAnswers,
|
||||||
|
showCorrectAnswers = showCorrectAnswers,
|
||||||
OneQuestionAtATime = oneQuestionAtATime,
|
OneQuestionAtATime = oneQuestionAtATime,
|
||||||
LocalAssignmentGroupName = assignmentGroup,
|
LocalAssignmentGroupName = assignmentGroup,
|
||||||
AllowedAttempts = allowedAttempts,
|
AllowedAttempts = allowedAttempts,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public class CanvasAssignmentGroupService
|
|||||||
private readonly MyLogger<CanvasAssignmentGroupService> logger;
|
private readonly MyLogger<CanvasAssignmentGroupService> logger;
|
||||||
|
|
||||||
public CanvasAssignmentGroupService(
|
public CanvasAssignmentGroupService(
|
||||||
IWebRequestor webRequestor,
|
IWebRequestor webRequestor,
|
||||||
CanvasServiceUtils utils,
|
CanvasServiceUtils utils,
|
||||||
MyLogger<CanvasAssignmentGroupService> logger
|
MyLogger<CanvasAssignmentGroupService> logger
|
||||||
)
|
)
|
||||||
@@ -73,4 +73,4 @@ public class CanvasAssignmentGroupService
|
|||||||
|
|
||||||
await webRequestor.PutAsync<CanvasAssignmentGroup>(request);
|
await webRequestor.PutAsync<CanvasAssignmentGroup>(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ public class CanvasQuizService(
|
|||||||
// assignment_group_id = "quiz", // TODO: support specific assignment groups
|
// assignment_group_id = "quiz", // TODO: support specific assignment groups
|
||||||
// time_limit = localQuiz.TimeLimit,
|
// time_limit = localQuiz.TimeLimit,
|
||||||
shuffle_answers = localQuiz.ShuffleAnswers,
|
shuffle_answers = localQuiz.ShuffleAnswers,
|
||||||
|
access_code = localQuiz.Password,
|
||||||
|
show_correct_answers = localQuiz.showCorrectAnswers,
|
||||||
// hide_results = localQuiz.HideResults,
|
// hide_results = localQuiz.HideResults,
|
||||||
allowed_attempts = localQuiz.AllowedAttempts,
|
allowed_attempts = localQuiz.AllowedAttempts,
|
||||||
one_question_at_a_time = false,
|
one_question_at_a_time = false,
|
||||||
@@ -91,11 +93,9 @@ public class CanvasQuizService(
|
|||||||
|
|
||||||
private async Task hackFixRedundantAssignments(ulong canvasCourseId)
|
private async Task hackFixRedundantAssignments(ulong canvasCourseId)
|
||||||
{
|
{
|
||||||
|
|
||||||
using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing redundant quiz assignments that are auto-created");
|
using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing redundant quiz assignments that are auto-created");
|
||||||
activity?.SetTag("canvas syncronization", true);
|
activity?.SetTag("canvas syncronization", true);
|
||||||
|
|
||||||
|
|
||||||
var canvasAssignments = await assignments.GetAll(canvasCourseId);
|
var canvasAssignments = await assignments.GetAll(canvasCourseId);
|
||||||
var assignmentsToDelete = canvasAssignments
|
var assignmentsToDelete = canvasAssignments
|
||||||
.Where(
|
.Where(
|
||||||
@@ -113,7 +113,7 @@ public class CanvasQuizService(
|
|||||||
a.Name
|
a.Name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
).ToArray();
|
||||||
await Task.WhenAll(tasks);
|
await Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class WebRequestor : IWebRequestor
|
|||||||
private RestClient client;
|
private RestClient client;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly ILogger<WebRequestor> logger;
|
private readonly ILogger<WebRequestor> logger;
|
||||||
|
private static int rateLimitRetryCount = 6;
|
||||||
|
private static int rateLimitSleepInterval = 1000;
|
||||||
|
|
||||||
public WebRequestor(IConfiguration config, ILogger<WebRequestor> logger)
|
public WebRequestor(IConfiguration config, ILogger<WebRequestor> logger)
|
||||||
{
|
{
|
||||||
@@ -34,39 +36,38 @@ public class WebRequestor : IWebRequestor
|
|||||||
return (deserialize<T>(response), response);
|
return (deserialize<T>(response), response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RestResponse> PostAsync(RestRequest request)
|
public async Task<RestResponse> PostAsync(RestRequest request) => await rateLimitAwarePostAsync(request, 0);
|
||||||
{
|
|
||||||
using var activity = DiagnosticsConfig.Source.StartActivity("sending post");
|
|
||||||
activity?.AddTag("success", false);
|
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<RestResponse> rateLimitAwarePostAsync(RestRequest request, int retryCount = 0)
|
||||||
|
{
|
||||||
request.AddHeader("Content-Type", "application/json");
|
request.AddHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
try
|
var response = await client.ExecutePostAsync(request);
|
||||||
{
|
|
||||||
var response = await client.ExecutePostAsync(request);
|
|
||||||
activity?.AddTag("url", response.ResponseUri);
|
|
||||||
|
|
||||||
if (isRateLimited(response))
|
if (isRateLimited(response)) {
|
||||||
logger.LogInformation("hit rate limit");
|
if(retryCount < rateLimitRetryCount){
|
||||||
|
logger.LogInformation($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
|
||||||
if (!response.IsSuccessful)
|
Console.WriteLine($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
|
||||||
{
|
Thread.Sleep(rateLimitSleepInterval);
|
||||||
logger.LogError($"Error with response, response content: {response.Content}", response);
|
return await rateLimitAwarePostAsync(request, retryCount + 1);}
|
||||||
throw new Exception("error with response");
|
|
||||||
}
|
|
||||||
activity?.AddTag("success", true);
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Console.WriteLine("inside post catch block");
|
|
||||||
throw e;
|
|
||||||
|
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
logger.LogError($"Error with response, response content: {response.Content}");
|
||||||
|
throw new Exception($"error post response, retrycount: {retryCount}, ratelimited: {isRateLimited(response)}, code: {response.StatusCode}, response content: {response.Content}");
|
||||||
}
|
}
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool isRateLimited(RestResponse response) =>
|
private static bool isRateLimited(RestResponse response)
|
||||||
response.StatusCode == HttpStatusCode.Forbidden && response.Content?.Contains("403 Forbidden (Rate Limit Exceeded)") != null;
|
{
|
||||||
|
if(response.Content == null)
|
||||||
|
return false;
|
||||||
|
return response.StatusCode == HttpStatusCode.Forbidden
|
||||||
|
&& response.Content.Contains("403 Forbidden (Rate Limit Exceeded)");
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
||||||
{
|
{
|
||||||
@@ -95,32 +96,33 @@ public class WebRequestor : IWebRequestor
|
|||||||
return (deserialize<T>(response), response);
|
return (deserialize<T>(response), response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RestResponse> DeleteAsync(RestRequest request)
|
public Task<RestResponse> DeleteAsync(RestRequest request) => recursiveDeleteAsync(request, 0);
|
||||||
|
private async Task<RestResponse> recursiveDeleteAsync(RestRequest request, int retryCount)
|
||||||
{
|
{
|
||||||
// using var activity = DiagnosticsConfig.Source.StartActivity($"sending delete web request");
|
|
||||||
// activity?.AddTag("success", false);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
var response = await client.DeleteAsync(request);
|
var response = await client.DeleteAsync(request);
|
||||||
if (isRateLimited(response))
|
if (isRateLimited(response))
|
||||||
Console.WriteLine("after delete response in rate limited");
|
Console.WriteLine("after delete response in rate limited");
|
||||||
// Console.WriteLine(response.Content);
|
|
||||||
// activity?.AddTag("url", response.ResponseUri);
|
|
||||||
// activity?.AddTag("success", true);
|
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (HttpRequestException e)
|
catch (HttpRequestException e)
|
||||||
{
|
{
|
||||||
if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)"
|
if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)"
|
||||||
logger.LogInformation("hit rate limit in delete");
|
{
|
||||||
|
if(retryCount < rateLimitRetryCount)
|
||||||
Console.WriteLine(e.StatusCode);
|
{
|
||||||
// Console.WriteLine();
|
logger.LogInformation($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
|
||||||
throw e;
|
Console.WriteLine($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
|
||||||
|
Thread.Sleep(rateLimitSleepInterval);
|
||||||
|
return await recursiveDeleteAsync(request, retryCount + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogInformation($"hit rate limit in delete, {rateLimitRetryCount} retries did not fix it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,4 +166,5 @@ public class WebRequestor : IWebRequestor
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ services:
|
|||||||
collector:
|
collector:
|
||||||
image: otel/opentelemetry-collector-contrib
|
image: otel/opentelemetry-collector-contrib
|
||||||
volumes:
|
volumes:
|
||||||
- ./ops/otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
|
- ./canvas-development/otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
|
||||||
ports:
|
ports:
|
||||||
- 1888:1888 # pprof extension
|
- 1888:1888 # pprof extension
|
||||||
- 8888:8888 # Prometheus metrics exposed by the Collector
|
- 8888:8888 # Prometheus metrics exposed by the Collector
|
||||||
|
|||||||
Reference in New Issue
Block a user