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}