001package edu.pdx.cs.joy.grader;
002
003import edu.pdx.cs.joy.grader.gradebook.Assignment;
004import edu.pdx.cs.joy.grader.gradebook.Grade;
005import edu.pdx.cs.joy.grader.gradebook.GradeBook;
006import edu.pdx.cs.joy.grader.gradebook.Student;
007import org.hamcrest.Description;
008import org.hamcrest.Matcher;
009import org.hamcrest.TypeSafeMatcher;
010import org.junit.jupiter.api.Test;
011
012import java.io.File;
013import java.time.LocalDateTime;
014import java.util.List;
015import java.util.Optional;
016import java.util.jar.Attributes;
017import java.util.jar.Manifest;
018
019import static org.hamcrest.MatcherAssert.assertThat;
020import static org.hamcrest.Matchers.*;
021import static org.junit.jupiter.api.Assertions.assertThrows;
022
023public class ProjectSubmissionsProcessorTest {
024
025  /**
026   * Given: A submission from a student whose name is in the grade book,
027   * but whose id is not
028   *
029   * When: The submission is recorded
030   *
031   * Then: The submission is noted under the expected id and a new Student
032   * is <b>not</b> created.
033   *
034   * This tests issue #52 (https://github.com/JoyOfCodingPDX/JoyOfCoding/issues/52)
035   */
036  @Test
037  public void matchStudentBasedOnFirstAndLastName() throws StudentEmailAttachmentProcessor.SubmissionException {
038    String projectName = "Project";
039
040    GradeBook gradebook = createGradeBookWithAssignment(projectName);
041    Student student = createStudentInGradeBook(gradebook);
042
043    String studentName = student.getFirstName() + " " + student.getLastName();
044    String wrongStudentId = "Not the student id we expect";
045    String wrongEmail = "Not the email that we expect";
046
047    Manifest manifest = manifest(projectName)
048      .setStudentName(studentName)
049      .setStudentId(wrongStudentId)
050      .setStudentEmail(wrongEmail)
051      .build();
052
053    noteProjectSubmissionInGradeBook(gradebook, manifest);
054
055    assertThat(gradebook.getStudent(wrongStudentId), isNotPresent());
056
057    assertThatProjectSubmissionWasRecordedForStudent(projectName, student);
058  }
059
060  @Test
061  public void matchStudentBasedOnNickAndLastName() throws StudentEmailAttachmentProcessor.SubmissionException {
062    String projectName = "Project";
063
064    GradeBook gradebook = createGradeBookWithAssignment(projectName);
065    Student student = createStudentInGradeBook(gradebook);
066
067    String studentName = student.getNickName() + " " + student.getLastName();
068    String wrongStudentId = "Not the student id we expect";
069    String wrongEmail = "Not the email that we expect";
070
071    Manifest manifest = manifest(projectName)
072      .setStudentName(studentName)
073      .setStudentId(wrongStudentId)
074      .setStudentEmail(wrongEmail)
075      .build();
076
077    noteProjectSubmissionInGradeBook(gradebook, manifest);
078
079    assertThat(gradebook.getStudent(wrongStudentId), isNotPresent());
080
081    assertThatProjectSubmissionWasRecordedForStudent(projectName, student);
082  }
083
084  @Test
085  public void matchStudentBasedOnEmail() throws StudentEmailAttachmentProcessor.SubmissionException {
086    String projectName = "Project";
087
088    GradeBook gradebook = createGradeBookWithAssignment(projectName);
089    Student student = createStudentInGradeBook(gradebook);
090
091    String studentName = "Not the student name we expect";
092    String wrongStudentId = "Not the student id we expect";
093    String email = student.getEmail();
094
095    Manifest manifest = manifest(projectName)
096      .setStudentName(studentName)
097      .setStudentId(wrongStudentId)
098      .setStudentEmail(email)
099      .build();
100
101    noteProjectSubmissionInGradeBook(gradebook, manifest);
102
103    assertThat(gradebook.getStudent(wrongStudentId), isNotPresent());
104
105    assertThatProjectSubmissionWasRecordedForStudent(projectName, student);
106  }
107
108  @Test
109  public void submissionDoesNotMatchAnyStudentInGradeBook() {
110    String projectName = "Project";
111
112    GradeBook gradebook = createGradeBookWithAssignment(projectName);
113
114    String studentName = "Not the student name we expect";
115    String wrongStudentId = "Not the student id we expect";
116    String wrongEmail = "Not the email we expect";
117
118    Manifest manifest = manifest(projectName)
119      .setStudentName(studentName)
120      .setStudentId(wrongStudentId)
121      .setStudentEmail(wrongEmail)
122      .build();
123
124    assertThrows(StudentEmailAttachmentProcessor.SubmissionException.class, () ->
125      noteProjectSubmissionInGradeBook(gradebook, manifest)
126    );
127  }
128
129  @Test
130  public void submissionTimeNotedInGradeBook() throws StudentEmailAttachmentProcessor.SubmissionException {
131    String projectName = "Project";
132
133    GradeBook gradebook = createGradeBookWithAssignment(projectName);
134    Student student = createStudentInGradeBook(gradebook);
135
136    LocalDateTime submissionDate = LocalDateTime.now().minusHours(2).withNano(0);
137    Manifest manifest = manifest(projectName)
138      .setStudent(student)
139      .setSubmissionDate(submissionDate)
140      .build();
141
142    noteProjectSubmissionInGradeBook(gradebook, manifest);
143
144    assertThat(student.getGrade(projectName).getSubmissionTimes(), contains(submissionDate));
145  }
146
147  @Test
148  public void estimatedHoursNotedInGradeBook() throws StudentEmailAttachmentProcessor.SubmissionException {
149    String projectName = "Project";
150
151    GradeBook gradebook = createGradeBookWithAssignment(projectName);
152    Student student = createStudentInGradeBook(gradebook);
153
154    Double estimatedHours = 4.5;
155    Manifest manifest = manifest(projectName)
156      .setStudent(student)
157      .setEstimatedHours(estimatedHours)
158      .build();
159
160    noteProjectSubmissionInGradeBook(gradebook, manifest);
161
162    List<Grade.SubmissionInfo> submissions = student.getGrade(projectName).getSubmissionInfos();
163    assertThat(submissions, hasSize(1));
164    assertThat(submissions.get(0).getEstimatedHours(), equalTo(estimatedHours));
165  }
166
167  @Test
168  public void submissionsPastDueDateAreLate() throws StudentEmailAttachmentProcessor.SubmissionException {
169    String projectName = "Project";
170    LocalDateTime dueDate = LocalDateTime.now();
171
172    GradeBook gradebook = createGradeBookWithAssignment(projectName, dueDate);
173    Student student = createStudentInGradeBook(gradebook);
174
175    LocalDateTime submissionDate = dueDate.plusDays(3);
176    Manifest manifest = manifest(projectName)
177      .setStudent(student)
178      .setSubmissionDate(submissionDate)
179      .build();
180
181    noteProjectSubmissionInGradeBook(gradebook, manifest);
182
183    assertThat(student.getLate(), contains(projectName));
184
185    Grade grade = student.getGrade(projectName);
186    assertThat(grade, notNullValue());
187    List<Grade.SubmissionInfo> submissions = grade.getSubmissionInfos();
188    assertThat(submissions, hasSize(1));
189    Grade.SubmissionInfo submission = submissions.get(0);
190    assertThat(submission.getSubmissionTime(), equalTo(submissionDate));
191    assertThat(submission.isLate(), equalTo(true));
192  }
193
194  @Test
195  public void submissionsBeforeDueDateAreNoteLate() throws StudentEmailAttachmentProcessor.SubmissionException {
196    String projectName = "Project";
197    LocalDateTime dueDate = LocalDateTime.now();
198
199    GradeBook gradebook = createGradeBookWithAssignment(projectName, dueDate);
200    Student student = createStudentInGradeBook(gradebook);
201
202    LocalDateTime submissionDate = dueDate.minusDays(3);
203    Manifest manifest = manifest(projectName)
204      .setStudent(student)
205      .setSubmissionDate(submissionDate)
206      .build();
207
208    noteProjectSubmissionInGradeBook(gradebook, manifest);
209
210    assertThat(student.getLate(), not(contains(projectName)));
211
212    Grade grade = student.getGrade(projectName);
213    assertThat(grade, notNullValue());
214    List<Grade.SubmissionInfo> submissions = grade.getSubmissionInfos();
215    assertThat(submissions, hasSize(1));
216    Grade.SubmissionInfo submission = submissions.get(0);
217    assertThat(submission.getSubmissionTime(), equalTo(submissionDate));
218    assertThat(submission.isLate(), equalTo(false));
219  }
220
221  private void noteProjectSubmissionInGradeBook(GradeBook gradebook, Manifest manifest) throws StudentEmailAttachmentProcessor.SubmissionException {
222    ProjectSubmissionsProcessor processor =
223      new ProjectSubmissionsProcessor(new File(System.getProperty("user.dir")), gradebook);
224    processor.noteSubmissionInGradeBook(manifest);
225  }
226
227  private void assertThatProjectSubmissionWasRecordedForStudent(String projectName, Student student) {
228    Grade grade = student.getGrade(projectName);
229    assertThat(grade, not(nullValue()));
230    assertThat(grade.isNotGraded(), equalTo(true));
231  }
232
233  private Student createStudentInGradeBook(GradeBook gradebook) {
234    Student student = new Student("studentId");
235    student.setFirstName("firstName");
236    student.setLastName("lastName");
237    student.setNickName("nickName");
238    student.setEmail("test@test.com");
239    gradebook.addStudent(student);
240    return student;
241  }
242
243  private GradeBook createGradeBookWithAssignment(String projectName) {
244    return createGradeBookWithAssignment(projectName, null);
245  }
246
247  private GradeBook createGradeBookWithAssignment(String projectName, LocalDateTime dueDate) {
248    GradeBook gradebook = new GradeBook("test");
249    Assignment assignment = new Assignment(projectName, 10.0);
250    assignment.setDueDate(dueDate);
251    gradebook.addAssignment(assignment);
252    return gradebook;
253  }
254
255  // Test match on email and nickname
256
257  private static Matcher<? super Optional<Student>> isNotPresent() {
258    return new TypeSafeMatcher<>() {
259      @Override
260      protected boolean matchesSafely(Optional<Student> item) {
261        return item.isEmpty();
262      }
263
264      @Override
265      public void describeTo(Description description) {
266        description.appendText("an Optional<Student> that is not present");
267      }
268    };
269  }
270
271  private ManifestBuilder manifest(String projectName) {
272    return new ManifestBuilder()
273      .setProjectName(projectName)
274      .setSubmissionDate(LocalDateTime.now())
275      .setSubmissionComment("Why is a comment required?")
276      ;
277  }
278
279  private static class ManifestBuilder {
280    Manifest manifest = new Manifest();
281
282    private ManifestBuilder setAttribute(Attributes.Name name, String value) {
283      this.manifest.getMainAttributes().put(name, value);
284      return this;
285    }
286
287    Manifest build() {
288      return manifest;
289    }
290
291    private ManifestBuilder setProjectName(String projectName) {
292      return setAttribute(Submit.ManifestAttributes.PROJECT_NAME, projectName);
293    }
294
295    public ManifestBuilder setStudent(Student student) {
296      return setStudentId(student.getId())
297        .setStudentName(student.getFullName())
298        .setStudentEmail(student.getEmail())
299        ;
300    }
301
302    private ManifestBuilder setStudentId(String studentId) {
303      return setAttribute(Submit.ManifestAttributes.USER_ID, studentId);
304    }
305
306    public ManifestBuilder setStudentName(String studentName) {
307      return setAttribute(Submit.ManifestAttributes.USER_NAME, studentName);
308    }
309
310    public ManifestBuilder setStudentEmail(String studentEmail) {
311      return setAttribute(Submit.ManifestAttributes.USER_EMAIL, studentEmail);
312    }
313
314    public ManifestBuilder setSubmissionDate(LocalDateTime submissionDate) {
315      return setSubmissionDate(Submit.ManifestAttributes.formatSubmissionTime(submissionDate));
316    }
317
318    private ManifestBuilder setSubmissionDate(String submissionDate) {
319      return setAttribute(Submit.ManifestAttributes.SUBMISSION_TIME, submissionDate);
320    }
321
322    public ManifestBuilder setSubmissionComment(String submissionComment) {
323      return setAttribute(Submit.ManifestAttributes.SUBMISSION_COMMENT, submissionComment);
324    }
325
326    public ManifestBuilder setEstimatedHours(Double estimatedHours) {
327      return setAttribute(Submit.ManifestAttributes.ESTIMATED_HOURS, String.valueOf(estimatedHours));
328    }
329  }
330
331}