001package edu.pdx.cs.joy.grader; 002 003import edu.pdx.cs.joy.grader.FindUngradedSubmissions.TestOutputDetailsProviderFromTestOutputFile; 004import org.junit.jupiter.api.Test; 005 006import java.nio.file.FileSystem; 007import java.nio.file.Files; 008import java.nio.file.Path; 009import java.nio.file.spi.FileSystemProvider; 010import java.time.LocalDateTime; 011import java.util.stream.Stream; 012 013import static org.hamcrest.MatcherAssert.assertThat; 014import static org.hamcrest.Matchers.equalTo; 015import static org.mockito.ArgumentMatchers.any; 016import static org.mockito.ArgumentMatchers.eq; 017import static org.mockito.Mockito.mock; 018import static org.mockito.Mockito.when; 019 020public class FindUngradedSubmissionsTest { 021 022 private static Path getPathToNonExistingFile() { 023 return getMockPath(false); 024 } 025 026 private Path getPathToExistingFile() { 027 return getMockPath(true); 028 } 029 030 private static Path getMockPath(boolean exists) { 031 Path testOutput = mock(Path.class); 032 033 FileSystemProvider provider = mock(FileSystemProvider.class); 034 when(provider.exists(testOutput)).thenReturn(exists); 035 036 FileSystem fileSystem = mock(FileSystem.class); 037 when(fileSystem.provider()).thenReturn(provider); 038 when(testOutput.getFileSystem()).thenReturn(fileSystem); 039 040 Path parent = mock(Path.class); 041 when(parent.getFileSystem()).thenReturn(fileSystem); 042 when(testOutput.getParent()).thenReturn(parent); 043 when(provider.exists(parent)).thenReturn(true); 044 045 assertThat(Files.exists(testOutput), equalTo(exists)); 046 return testOutput; 047 } 048 049 @Test 050 void submissionWithNoTestOutputNeedsToBeTested() { 051 FindUngradedSubmissions.SubmissionDetailsProvider submissionDetailsProvider = mock(FindUngradedSubmissions.SubmissionDetailsProvider.class); 052 053 String studentId = "student123"; 054 FindUngradedSubmissions.SubmissionDetails submissionDetails = new FindUngradedSubmissions.SubmissionDetails(studentId, LocalDateTime.now()); 055 Path submission = getPathToExistingFile(); 056 when(submissionDetailsProvider.getSubmissionDetails(submission)).thenReturn(submissionDetails); 057 058 Path testOutput = getPathToNonExistingFile(); 059 060 FindUngradedSubmissions.TestOutputPathProvider testOutputProvider = mock(FindUngradedSubmissions.TestOutputPathProvider.class); 061 when(testOutputProvider.getTestOutput(any(Path.class), eq(studentId))).thenReturn(testOutput); 062 063 FindUngradedSubmissions finder = new FindUngradedSubmissions(submissionDetailsProvider, testOutputProvider, mock(FindUngradedSubmissions.TestOutputDetailsProvider.class)); 064 FindUngradedSubmissions.SubmissionAnalysis analysis = finder.analyzeSubmission(submission); 065 assertThat(analysis.needsToBeTested(), equalTo(true)); 066 assertThat(analysis.needsToBeGraded(), equalTo(true)); 067 assertThat(analysis.submission(), equalTo(submission)); 068 } 069 070 @Test 071 void submissionWithTestOutputOlderThanSubmissionNeedsToBeTested() { 072 FindUngradedSubmissions.SubmissionDetailsProvider submissionDetailsProvider = mock(FindUngradedSubmissions.SubmissionDetailsProvider.class); 073 074 String studentId = "student123"; 075 LocalDateTime submissionTime = LocalDateTime.now(); 076 FindUngradedSubmissions.SubmissionDetails submissionDetails = new FindUngradedSubmissions.SubmissionDetails(studentId, submissionTime); 077 Path submission = getPathToExistingFile(); 078 when(submissionDetailsProvider.getSubmissionDetails(submission)).thenReturn(submissionDetails); 079 080 Path testOutput = getPathToExistingFile(); 081 082 FindUngradedSubmissions.TestOutputPathProvider testOutputProvider = mock(FindUngradedSubmissions.TestOutputPathProvider.class); 083 when(testOutputProvider.getTestOutput(any(Path.class), eq(studentId))).thenReturn(testOutput); 084 085 FindUngradedSubmissions.TestOutputDetailsProvider testOutputDetailsProvider = mock(FindUngradedSubmissions.TestOutputDetailsProvider.class); 086 LocalDateTime testedSubmissionTime = submissionTime.minusDays(1); // Simulate test output older than submission 087 when(testOutputDetailsProvider.getTestOutputDetails(testOutput)).thenReturn(new FindUngradedSubmissions.TestOutputDetails(testedSubmissionTime, true)); 088 089 FindUngradedSubmissions finder = new FindUngradedSubmissions(submissionDetailsProvider, testOutputProvider, testOutputDetailsProvider); 090 FindUngradedSubmissions.SubmissionAnalysis analysis = finder.analyzeSubmission(submission); 091 assertThat(analysis.needsToBeTested(), equalTo(true)); 092 assertThat(analysis.needsToBeGraded(), equalTo(true)); 093 } 094 095 @Test 096 void submissionWithTestOutputLessThanAMinuteOlderThanSubmissionDoesNotNeedToBeTested() { 097 FindUngradedSubmissions.SubmissionDetailsProvider submissionDetailsProvider = mock(FindUngradedSubmissions.SubmissionDetailsProvider.class); 098 099 String studentId = "student123"; 100 LocalDateTime submissionTime = LocalDateTime.now(); 101 FindUngradedSubmissions.SubmissionDetails submissionDetails = new FindUngradedSubmissions.SubmissionDetails(studentId, submissionTime); 102 Path submission = getPathToExistingFile(); 103 when(submissionDetailsProvider.getSubmissionDetails(submission)).thenReturn(submissionDetails); 104 105 Path testOutput = getPathToExistingFile(); 106 107 FindUngradedSubmissions.TestOutputPathProvider testOutputProvider = mock(FindUngradedSubmissions.TestOutputPathProvider.class); 108 when(testOutputProvider.getTestOutput(any(Path.class), eq(studentId))).thenReturn(testOutput); 109 110 FindUngradedSubmissions.TestOutputDetailsProvider testOutputDetailsProvider = mock(FindUngradedSubmissions.TestOutputDetailsProvider.class); 111 LocalDateTime testedSubmissionTime = submissionTime.minusSeconds(10); // Simulate test output older than submission 112 when(testOutputDetailsProvider.getTestOutputDetails(testOutput)).thenReturn(new FindUngradedSubmissions.TestOutputDetails(testedSubmissionTime, true)); 113 114 FindUngradedSubmissions finder = new FindUngradedSubmissions(submissionDetailsProvider, testOutputProvider, testOutputDetailsProvider); 115 FindUngradedSubmissions.SubmissionAnalysis analysis = finder.analyzeSubmission(submission); 116 assertThat(analysis.needsToBeTested(), equalTo(false)); 117 } 118 119 @Test 120 void submissionWithNoGradeNeedsToBeGraded() { 121 FindUngradedSubmissions.SubmissionDetailsProvider submissionDetailsProvider = mock(FindUngradedSubmissions.SubmissionDetailsProvider.class); 122 123 String studentId = "student123"; 124 LocalDateTime submissionTime = LocalDateTime.now(); 125 FindUngradedSubmissions.SubmissionDetails submissionDetails = new FindUngradedSubmissions.SubmissionDetails(studentId, submissionTime); 126 Path submission = getPathToExistingFile(); 127 when(submissionDetailsProvider.getSubmissionDetails(submission)).thenReturn(submissionDetails); 128 129 Path testOutput = getPathToExistingFile(); 130 131 FindUngradedSubmissions.TestOutputPathProvider testOutputProvider = mock(FindUngradedSubmissions.TestOutputPathProvider.class); 132 when(testOutputProvider.getTestOutput(any(Path.class), eq(studentId))).thenReturn(testOutput); 133 134 FindUngradedSubmissions.TestOutputDetailsProvider testOutputDetailsProvider = mock(FindUngradedSubmissions.TestOutputDetailsProvider.class); 135 LocalDateTime gradedTime = submissionTime.plusDays(1); // Simulate test output newer than submission 136 when(testOutputDetailsProvider.getTestOutputDetails(testOutput)).thenReturn(new FindUngradedSubmissions.TestOutputDetails(gradedTime, false)); 137 138 FindUngradedSubmissions finder = new FindUngradedSubmissions(submissionDetailsProvider, testOutputProvider, testOutputDetailsProvider); 139 FindUngradedSubmissions.SubmissionAnalysis analysis = finder.analyzeSubmission(submission); 140 assertThat(analysis.needsToBeTested(), equalTo(false)); 141 assertThat(analysis.needsToBeGraded(), equalTo(true)); 142 } 143 144 @Test 145 void submissionWithGradeIsGraded() { 146 FindUngradedSubmissions.SubmissionDetailsProvider submissionDetailsProvider = mock(FindUngradedSubmissions.SubmissionDetailsProvider.class); 147 148 String studentId = "student123"; 149 LocalDateTime submissionTime = LocalDateTime.now(); 150 FindUngradedSubmissions.SubmissionDetails submissionDetails = new FindUngradedSubmissions.SubmissionDetails(studentId, submissionTime); 151 Path submission = getPathToExistingFile(); 152 when(submissionDetailsProvider.getSubmissionDetails(submission)).thenReturn(submissionDetails); 153 154 Path testOutput = getPathToExistingFile(); 155 156 FindUngradedSubmissions.TestOutputPathProvider testOutputProvider = mock(FindUngradedSubmissions.TestOutputPathProvider.class); 157 when(testOutputProvider.getTestOutput(any(Path.class), eq(studentId))).thenReturn(testOutput); 158 159 FindUngradedSubmissions.TestOutputDetailsProvider testOutputDetailsProvider = mock(FindUngradedSubmissions.TestOutputDetailsProvider.class); 160 LocalDateTime gradedTime = submissionTime.plusDays(1); 161 when(testOutputDetailsProvider.getTestOutputDetails(testOutput)).thenReturn(new FindUngradedSubmissions.TestOutputDetails(gradedTime, true)); 162 163 FindUngradedSubmissions finder = new FindUngradedSubmissions(submissionDetailsProvider, testOutputProvider, testOutputDetailsProvider); 164 FindUngradedSubmissions.SubmissionAnalysis analysis = finder.analyzeSubmission(submission); 165 assertThat(analysis.needsToBeTested(), equalTo(false)); 166 assertThat(analysis.needsToBeGraded(), equalTo(false)); 167 } 168 169 @Test 170 void parseSubmissionTimeFromAndroidProjectTestOutputLine() { 171 LocalDateTime submissionTime = TestOutputDetailsProviderFromTestOutputFile.parseSubmissionTime(" Submitted on 2025-08-18T11:34:19.017953486"); 172 LocalDateTime expectedTime = LocalDateTime.of(2025, 8, 18, 11, 34, 19, 17953486); 173 assertThat(submissionTime, equalTo(expectedTime)); 174 } 175 176 @Test 177 void parseSubmissionTimeFromTestOutputLine() { 178 LocalDateTime submissionTime = TestOutputDetailsProviderFromTestOutputFile.parseSubmissionTime(" Submitted on Wed Aug 6 01:13:59 PM PDT 2025"); 179 LocalDateTime expectedTime = LocalDateTime.of(2025, 8, 6, 13, 13, 59); 180 assertThat(submissionTime, equalTo(expectedTime)); 181 } 182 183 @Test 184 void parseSubmissionTimeFromTestOutputLineWithTwoDigitDay() { 185 LocalDateTime submissionTime = TestOutputDetailsProviderFromTestOutputFile.parseSubmissionTime(" Submitted on Wed Jul 23 12:59:13 PM PDT 2025"); 186 LocalDateTime expectedTime = LocalDateTime.of(2025, 7, 23, 12, 59, 13); 187 assertThat(submissionTime, equalTo(expectedTime)); 188 } 189 190 @Test 191 void lineWithGradeHasGrade() { 192 String line = "12.5 out of 13.0"; 193 Double grade = TestOutputDetailsProviderFromTestOutputFile.parseGrade(line); 194 assertThat(grade, equalTo(12.5)); 195 } 196 197 @Test 198 void lineWithNoGradeHasNoGrade() { 199 String line = "No grade"; 200 Double grade = TestOutputDetailsProviderFromTestOutputFile.parseGrade(line); 201 assertThat(grade, equalTo(null)); 202 } 203 204 @Test 205 void lineWithMissingGradeHasNaNGrade() { 206 String line = " out of 13.0"; 207 Double nan = TestOutputDetailsProviderFromTestOutputFile.parseGrade(line); 208 assertThat(nan, equalTo(Double.NaN)); 209 } 210 211 @Test 212 void parseTestOutputDetails() { 213 Stream<String> lines = Stream.of( 214 " Submitted on Wed Aug 6 01:13:59 PM PDT 2025", 215 "", 216 "12.5 out of 13.0" 217 ); 218 FindUngradedSubmissions.TestOutputDetails details = TestOutputDetailsProviderFromTestOutputFile.parseTestOutputDetails(lines); 219 LocalDateTime submissionTime = LocalDateTime.of(2025, 8, 6, 13, 13, 59); 220 assertThat(details.testedSubmissionTime(), equalTo(submissionTime)); 221 assertThat(details.hasGrade(), equalTo(true)); 222 } 223 224 @Test 225 void testOutputDetailsWithMessageToStudentDoesNotNeedToBeGraded() { 226 Stream<String> lines = Stream.of( 227 "Hi, Student. There were some problems with your submission", 228 "", 229 "I ran it through the testing script and there are a couple of things", 230 "I'd like you to fix before I ask the Graders to score it", 231 "", 232 " The Joy of Coding Project 3: edu.pdx.cs.joy.student.Project3", 233 " Submitted by Student Name", 234 " Submitted on Wed Jul 30 05:10:26 PM PDT 2025", 235 " Graded on Wed Jul 30 05:41:04 PM PDT 2025", 236 "", 237 " out of 7.0" 238 ); 239 FindUngradedSubmissions.TestOutputDetails details = TestOutputDetailsProviderFromTestOutputFile.parseTestOutputDetails(lines); 240 assertThat(details.hasGrade(), equalTo(true)); 241 242 } 243}