SyncCourseWithPlRepoJob.java

1
package edu.ucsb.cs.scaffold.jobs;
2
3
import com.fasterxml.jackson.databind.JsonNode;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5
import edu.ucsb.cs.scaffold.entity.Course;
6
import edu.ucsb.cs.scaffold.entity.PatCredential;
7
import edu.ucsb.cs.scaffold.entity.PlAssessment;
8
import edu.ucsb.cs.scaffold.entity.PlAssessmentQuestion;
9
import edu.ucsb.cs.scaffold.entity.PlAssessmentSet;
10
import edu.ucsb.cs.scaffold.entity.PlInstance;
11
import edu.ucsb.cs.scaffold.entity.PlQuestion;
12
import edu.ucsb.cs.scaffold.entity.PlRepo;
13
import edu.ucsb.cs.scaffold.enums.PatPlatform;
14
import edu.ucsb.cs.scaffold.errors.EntityNotFoundException;
15
import edu.ucsb.cs.scaffold.repository.PatCredentialRepository;
16
import edu.ucsb.cs.scaffold.repository.PlAssessmentQuestionRepository;
17
import edu.ucsb.cs.scaffold.repository.PlAssessmentRepository;
18
import edu.ucsb.cs.scaffold.repository.PlAssessmentSetRepository;
19
import edu.ucsb.cs.scaffold.repository.PlInstanceRepository;
20
import edu.ucsb.cs.scaffold.repository.PlQuestionRepository;
21
import edu.ucsb.cs.scaffold.repository.PlRepoRepository;
22
import edu.ucsb.cs.scaffold.repository.PlScaffoldAssessmentRepository;
23
import edu.ucsb.cs.scaffold.services.GithubService;
24
import edu.ucsb.cs.scaffold.services.GithubService.DirectoryEntry;
25
import edu.ucsb.cs.scaffold.services.PatEncryptionService;
26
import edu.ucsb.cs.scaffold.services.PrairieLearnService;
27
import edu.ucsb.cs156.jobs.services.JobContext;
28
import edu.ucsb.cs156.jobs.services.JobContextConsumer;
29
import java.util.ArrayList;
30
import java.util.LinkedHashMap;
31
import java.util.LinkedHashSet;
32
import java.util.List;
33
import java.util.Map;
34
import java.util.Objects;
35
import java.util.Optional;
36
import java.util.Set;
37
import java.util.UUID;
38
import lombok.Builder;
39
import org.springframework.web.client.HttpClientErrorException;
40
41
/**
42
 * Syncs one course's PrairieLearn state (issue #69), replacing the repo-wide SyncPlRepoJob. Takes a
43
 * course (rather than a PlRepo id) and uses the launching user's GitHub and PrairieLearn PATs.
44
 *
45
 * <p>Before doing any work, the job verifies that the user has both PATs, that the course is
46
 * associated with a GitHub repo and a PrairieLearn course instance, and that the PATs can actually
47
 * reach both — read/write access to the repo, and access to the course instance via the
48
 * PrairieLearn API. Any failure terminates the job with a log message telling the user where to fix
49
 * it (the /profile page for PATs, the PrairieLearn tab of the course settings for the
50
 * associations).
51
 *
52
 * <p>Unlike the old job, the PlInstance table is not repopulated from the repo's courseInstances
53
 * directory: the course's instance already exists, and its shortName/longName are just
54
 * sanity-checked (and corrected) against what PrairieLearn reports. Assessments are synced only for
55
 * the course's own instance. Questions are still traversed for the whole repo, exactly as before:
56
 * the {@code questions} directory is walked recursively; a directory containing an {@code
57
 * info.json} is a question whose questionId is its path relative to {@code questions}; {@code
58
 * __drafts__} directories are skipped; question directories are not traversed further; stale
59
 * PlQuestion rows are deleted (cascading to their PlScaffoldAssessments and join rows).
60
 *
61
 * <p>Assessment sets (issue #93): the {@code assessmentSets} array of the repo's top-level {@code
62
 * infoCourse.json} becomes the PlAssessmentSet rows of the repo (assessment sets are global to the
63
 * whole course, not per-instance), matched by abbreviation. Stale rows are deleted.
64
 *
65
 * <p>Assessments: each subdirectory of {@code courseInstances/<instance>/assessments} containing an
66
 * {@code infoAssessment.json} becomes a PlAssessment; the {@code zones} key is walked recursively
67
 * and every {@code "id"} entry links the assessment to a PlQuestion of the repo (the
68
 * PlAssessmentQuestion join table, in zone order, rewritten on change). Stale rows are deleted.
69
 *
70
 * <p>Finally the job asks the PrairieLearn API for the instance's assessments and copies the fields
71
 * from issue #71 (numeric id, number, order, title, and the assessment-set
72
 * abbreviation/number/heading/color) onto the matching PlAssessment rows, matched by name.
73
 *
74
 * <p>If the {@code questions} or per-instance {@code assessments} directory is missing (HTTP 404),
75
 * that step is skipped entirely — including deletions — because a 404 cannot distinguish "directory
76
 * removed" from "token cannot see the repo", and mass-deleting rows over a token problem would be
77
 * wrong.
78
 */
79
@Builder
80
public class SyncCourseWithPlRepoJob implements JobContextConsumer {
81
82
  static final String COURSE_INSTANCES_PATH = "courseInstances";
83
  static final String QUESTIONS_PATH = "questions";
84
  static final String ASSESSMENTS_DIRECTORY = "assessments";
85
  static final String DRAFTS_DIRECTORY = "__drafts__";
86
  static final String INFO_JSON = "info.json";
87
  static final String INFO_ASSESSMENT_JSON = "infoAssessment.json";
88
  static final String INFO_COURSE_JSON = "infoCourse.json";
89
  static final String ASSESSMENT_SETS_KEY = "assessmentSets";
90
91
  private static final ObjectMapper MAPPER = new ObjectMapper();
92
93
  private long userId;
94
  private Course course;
95
  private PatCredentialRepository patCredentialRepository;
96
  private PatEncryptionService patEncryptionService;
97
  private PlRepoRepository plRepoRepository;
98
  private PlInstanceRepository plInstanceRepository;
99
  private PlQuestionRepository plQuestionRepository;
100
  private PlScaffoldAssessmentRepository plScaffoldAssessmentRepository;
101
  private PlAssessmentRepository plAssessmentRepository;
102
  private PlAssessmentQuestionRepository plAssessmentQuestionRepository;
103
  private PlAssessmentSetRepository plAssessmentSetRepository;
104
  private GithubService githubService;
105
  private PrairieLearnService prairieLearnService;
106
107
  /** The uuid and title of a question, as read from its info.json. */
108
  record QuestionInfo(UUID uuid, String title) {}
109
110
  @Override
111
  public String getScopeType() {
112 1 1. getScopeType : replaced return value with "" for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::getScopeType → KILLED
    return "course";
113
  }
114
115
  @Override
116
  public Long getScopeId() {
117 1 1. getScopeId : replaced Long return value with 0L for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::getScopeId → KILLED
    return course.getId();
118
  }
119
120
  @Override
121
  public void accept(JobContext ctx) throws Exception {
122 1 1. accept : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
    ctx.log(
123
        "Syncing course %d (%s) with PrairieLearn"
124
            .formatted(course.getId(), course.getCourseName()));
125
126
    // (1) Both PATs must be configured before anything else.
127
    Optional<PatCredential> githubCredential =
128
        patCredentialRepository.findByUserIdAndPlatform(userId, PatPlatform.GITHUB);
129
    Optional<PatCredential> plCredential =
130
        patCredentialRepository.findByUserIdAndPlatform(userId, PatPlatform.PRAIRIELEARN);
131 2 1. accept : negated conditional → KILLED
2. accept : negated conditional → KILLED
    if (githubCredential.isEmpty() || plCredential.isEmpty()) {
132
      List<String> missing = new ArrayList<>();
133 1 1. accept : negated conditional → KILLED
      if (githubCredential.isEmpty()) {
134
        missing.add("GitHub PAT");
135
      }
136 1 1. accept : negated conditional → KILLED
      if (plCredential.isEmpty()) {
137
        missing.add("PrairieLearn PAT");
138
      }
139
      throw new Exception(
140
          "Missing %s: set it up on the /profile page before running this job"
141
              .formatted(String.join(" and ", missing)));
142
    }
143
144
    // (2) The course must be associated with a repo and a course instance.
145 2 1. accept : negated conditional → KILLED
2. accept : negated conditional → KILLED
    if (course.getPlRepoId() == null || course.getPlInstanceId() == null) {
146
      List<String> missing = new ArrayList<>();
147 1 1. accept : negated conditional → KILLED
      if (course.getPlRepoId() == null) {
148
        missing.add("a GitHub repo");
149
      }
150 1 1. accept : negated conditional → KILLED
      if (course.getPlInstanceId() == null) {
151
        missing.add("a PrairieLearn course instance");
152
      }
153
      throw new Exception(
154
          "This course is not associated with %s yet; set that up on the PrairieLearn tab of the course settings page"
155
              .formatted(String.join(" or ", missing)));
156
    }
157
    PlRepo plRepo =
158
        plRepoRepository
159
            .findById(course.getPlRepoId())
160 1 1. lambda$accept$0 : replaced return value with null for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$accept$0 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(PlRepo.class, course.getPlRepoId()));
161
    PlInstance plInstance =
162
        plInstanceRepository
163
            .findById(course.getPlInstanceId())
164
            .orElseThrow(
165 1 1. lambda$accept$1 : replaced return value with null for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$accept$1 → KILLED
                () -> new EntityNotFoundException(PlInstance.class, course.getPlInstanceId()));
166 1 1. accept : negated conditional → KILLED
    if (plInstance.getNumericId() == null) {
167
      throw new Exception(
168
          "The course's PrairieLearn instance has no numeric id yet; re-associate it on the"
169
              + " PrairieLearn tab of the course settings page");
170
    }
171
172
    String githubToken =
173
        patEncryptionService.decrypt(
174
            githubCredential.get().getCiphertext(), githubCredential.get().getKeyVersion());
175
    String plToken =
176
        patEncryptionService.decrypt(
177
            plCredential.get().getCiphertext(), plCredential.get().getKeyVersion());
178
179
    // (3) Sanity-check access with both PATs before touching any data.
180
    boolean canWrite;
181
    try {
182
      canWrite = githubService.hasWriteAccess(plRepo.getRepoName(), githubToken);
183
    } catch (HttpClientErrorException e) {
184
      throw new Exception(
185
          "The stored GitHub PAT cannot read repo %s (HTTP %d); check the token on the /profile page and the repo on the PrairieLearn tab"
186
              .formatted(plRepo.getRepoName(), e.getStatusCode().value()));
187
    }
188 1 1. accept : negated conditional → KILLED
    if (!canWrite) {
189
      throw new Exception(
190
          "The stored GitHub PAT has read-only access to repo %s; read/write access is required"
191
              .formatted(plRepo.getRepoName()));
192
    }
193
    PrairieLearnService.CourseInstanceInfo instanceInfo;
194
    try {
195
      instanceInfo = prairieLearnService.getCourseInstance(plInstance.getNumericId(), plToken);
196
    } catch (HttpClientErrorException e) {
197
      throw new Exception(
198
          "The stored PrairieLearn PAT cannot access course instance %d (HTTP %d); check the token on the /profile page"
199
              .formatted(plInstance.getNumericId(), e.getStatusCode().value()));
200
    }
201 1 1. accept : negated conditional → KILLED
    if (instanceInfo == null) {
202
      throw new Exception(
203
          "PrairieLearn returned no data for course instance %d"
204
              .formatted(plInstance.getNumericId()));
205
    }
206 1 1. accept : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
    ctx.log(
207
        "Access verified: repo %s (read/write) and PrairieLearn instance %d"
208
            .formatted(plRepo.getRepoName(), plInstance.getNumericId()));
209
210
    // (4) Sanity-check the instance metadata instead of repopulating the PlInstance table.
211 1 1. accept : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::sanityCheckInstance → KILLED
    sanityCheckInstance(ctx, plInstance, instanceInfo);
212
213
    try {
214 1 1. accept : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncAssessmentSets → KILLED
      syncAssessmentSets(ctx, plRepo, githubToken);
215 1 1. accept : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncQuestions → KILLED
      syncQuestions(ctx, plRepo, githubToken);
216 1 1. accept : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncAssessments → KILLED
      syncAssessments(ctx, plRepo, plInstance, githubToken);
217
    } catch (HttpClientErrorException.Unauthorized | HttpClientErrorException.Forbidden e) {
218
      throw new Exception(
219
          "GitHub rejected the stored PAT (HTTP %d). The token may be expired, revoked, or not approved for this repo; enter a new one (see docs/Github_PAT.md)"
220
              .formatted(e.getStatusCode().value()));
221
    }
222
223
    // (5) Copy the PrairieLearn-side assessment fields onto the matching rows.
224 1 1. accept : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::enrichAssessmentsFromPrairieLearn → KILLED
    enrichAssessmentsFromPrairieLearn(ctx, plRepo, plInstance, plToken);
225
  }
226
227
  /**
228
   * Keeps the stored shortName/longName in line with what PrairieLearn reports for the instance's
229
   * numeric id — the association was verified when it was created, so a difference here means the
230
   * instance was renamed on the PrairieLearn side.
231
   */
232
  private void sanityCheckInstance(
233
      JobContext ctx, PlInstance plInstance, PrairieLearnService.CourseInstanceInfo info) {
234
    boolean changed = false;
235 1 1. sanityCheckInstance : negated conditional → KILLED
    if (!Objects.equals(info.shortName(), plInstance.getShortName())) {
236 1 1. sanityCheckInstance : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
237
          "Instance shortName changed on PrairieLearn: %s -> %s"
238
              .formatted(plInstance.getShortName(), info.shortName()));
239 1 1. sanityCheckInstance : removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setShortName → KILLED
      plInstance.setShortName(info.shortName());
240
      changed = true;
241
    }
242 1 1. sanityCheckInstance : negated conditional → KILLED
    if (!Objects.equals(info.longName(), plInstance.getLongName())) {
243 1 1. sanityCheckInstance : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
244
          "Instance longName changed on PrairieLearn: %s -> %s"
245
              .formatted(plInstance.getLongName(), info.longName()));
246 1 1. sanityCheckInstance : removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setLongName → KILLED
      plInstance.setLongName(info.longName());
247
      changed = true;
248
    }
249 1 1. sanityCheckInstance : negated conditional → KILLED
    if (changed) {
250
      plInstanceRepository.save(plInstance);
251
    } else {
252 1 1. sanityCheckInstance : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log("Instance %s metadata verified".formatted(plInstance.getShortName()));
253
    }
254
  }
255
256
  /**
257
   * Syncs the repo's assessment sets (issue #93) from the {@code assessmentSets} array of the
258
   * top-level {@code infoCourse.json} — assessment sets are global to the whole course repo, not
259
   * per-instance. Each entry becomes a PlAssessmentSet row, matched by abbreviation; stale rows
260
   * (abbreviations no longer present) are deleted. If the file is missing (HTTP 404) or cannot be
261
   * parsed, the sync is skipped entirely so a token/access problem cannot mass-delete rows.
262
   */
263
  private void syncAssessmentSets(JobContext ctx, PlRepo plRepo, String token) {
264
    String content;
265
    try {
266
      content = githubService.getFileContent(plRepo.getRepoName(), INFO_COURSE_JSON, token);
267
    } catch (HttpClientErrorException.NotFound e) {
268 1 1. syncAssessmentSets : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
269
          "Repo %s has no %s; skipping assessment set sync"
270
              .formatted(plRepo.getRepoName(), INFO_COURSE_JSON));
271
      return;
272
    }
273
274
    JsonNode root;
275
    try {
276
      root = MAPPER.readTree(content);
277
    } catch (Exception e) {
278 1 1. syncAssessmentSets : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
279
          "Skipping assessment set sync for repo %s: could not parse %s"
280
              .formatted(plRepo.getRepoName(), INFO_COURSE_JSON));
281
      return;
282
    }
283
284
    JsonNode assessmentSets = root.get(ASSESSMENT_SETS_KEY);
285 1 1. syncAssessmentSets : negated conditional → KILLED
    if (assessmentSets == null) {
286 1 1. syncAssessmentSets : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
287
          "Repo %s's %s has no %s key; skipping assessment set sync"
288
              .formatted(plRepo.getRepoName(), INFO_COURSE_JSON, ASSESSMENT_SETS_KEY));
289
      return;
290
    }
291 1 1. syncAssessmentSets : negated conditional → KILLED
    if (!assessmentSets.isArray()) {
292 1 1. syncAssessmentSets : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
293
          "Repo %s's %s has a non-array %s value; skipping assessment set sync"
294
              .formatted(plRepo.getRepoName(), INFO_COURSE_JSON, ASSESSMENT_SETS_KEY));
295
      return;
296
    }
297
298
    Map<String, PlAssessmentSet> existingByAbbreviation = new LinkedHashMap<>();
299
    for (PlAssessmentSet set : plAssessmentSetRepository.findByPlRepoId(plRepo.getId())) {
300
      existingByAbbreviation.put(set.getAbbreviation(), set);
301
    }
302
303
    int added = 0;
304
    int updated = 0;
305
    int unchanged = 0;
306
    int skipped = 0;
307
    Set<String> foundAbbreviations = new LinkedHashSet<>();
308
    for (JsonNode entry : assessmentSets) {
309
      String abbreviation = entry.path("abbreviation").asText(null);
310
      String name = entry.path("name").asText(null);
311
      String heading = entry.path("heading").asText(null);
312
      String color = entry.path("color").asText(null);
313 4 1. syncAssessmentSets : negated conditional → KILLED
2. syncAssessmentSets : negated conditional → KILLED
3. syncAssessmentSets : negated conditional → KILLED
4. syncAssessmentSets : negated conditional → KILLED
      if (abbreviation == null || name == null || heading == null || color == null) {
314 1 1. syncAssessmentSets : Changed increment from 1 to -1 → KILLED
        skipped++;
315 1 1. syncAssessmentSets : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log(
316
            "Skipping assessment set entry %s (repo %s): missing abbreviation, name, heading, or color"
317
                .formatted(entry, plRepo.getRepoName()));
318
        continue;
319
      }
320
      foundAbbreviations.add(abbreviation);
321
322
      PlAssessmentSet set = existingByAbbreviation.get(abbreviation);
323 1 1. syncAssessmentSets : negated conditional → KILLED
      if (set == null) {
324
        plAssessmentSetRepository.save(
325
            PlAssessmentSet.builder()
326
                .plRepoId(plRepo.getId())
327
                .abbreviation(abbreviation)
328
                .name(name)
329
                .heading(heading)
330
                .color(color)
331
                .build());
332 1 1. syncAssessmentSets : Changed increment from 1 to -1 → KILLED
        added++;
333 1 1. syncAssessmentSets : negated conditional → KILLED
      } else if (!Objects.equals(set.getName(), name)
334 1 1. syncAssessmentSets : negated conditional → KILLED
          || !Objects.equals(set.getHeading(), heading)
335 1 1. syncAssessmentSets : negated conditional → KILLED
          || !Objects.equals(set.getColor(), color)) {
336 1 1. syncAssessmentSets : removed call to edu/ucsb/cs/scaffold/entity/PlAssessmentSet::setName → KILLED
        set.setName(name);
337 1 1. syncAssessmentSets : removed call to edu/ucsb/cs/scaffold/entity/PlAssessmentSet::setHeading → KILLED
        set.setHeading(heading);
338 1 1. syncAssessmentSets : removed call to edu/ucsb/cs/scaffold/entity/PlAssessmentSet::setColor → KILLED
        set.setColor(color);
339
        plAssessmentSetRepository.save(set);
340 1 1. syncAssessmentSets : Changed increment from 1 to -1 → KILLED
        updated++;
341
      } else {
342 1 1. syncAssessmentSets : Changed increment from 1 to -1 → KILLED
        unchanged++;
343
      }
344
    }
345
346
    int deleted = 0;
347
    for (String abbreviation : existingByAbbreviation.keySet()) {
348 1 1. syncAssessmentSets : negated conditional → KILLED
      if (!foundAbbreviations.contains(abbreviation)) {
349 1 1. syncAssessmentSets : removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentSetRepository::delete → KILLED
        plAssessmentSetRepository.delete(existingByAbbreviation.get(abbreviation));
350 1 1. syncAssessmentSets : Changed increment from 1 to -1 → KILLED
        deleted++;
351
      }
352
    }
353
354 1 1. syncAssessmentSets : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
    ctx.log(
355
        "Assessment sets: %d added, %d updated, %d deleted, %d unchanged, %d skipped"
356
            .formatted(added, updated, deleted, unchanged, skipped));
357
  }
358
359
  private void syncQuestions(JobContext ctx, PlRepo plRepo, String token) {
360
    Map<String, QuestionInfo> foundQuestions = new LinkedHashMap<>();
361
    try {
362 1 1. syncQuestions : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::walkQuestionsDirectory → KILLED
      walkQuestionsDirectory(ctx, plRepo, token, QUESTIONS_PATH, "", foundQuestions);
363
    } catch (HttpClientErrorException.NotFound e) {
364 1 1. syncQuestions : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
365
          "Repo %s has no %s directory (or the token cannot see the repo); skipping question sync"
366
              .formatted(plRepo.getRepoName(), QUESTIONS_PATH));
367
      return;
368
    }
369
370
    Map<String, PlQuestion> existingByQuestionId = new LinkedHashMap<>();
371
    for (PlQuestion question : plQuestionRepository.findByPlRepoId(plRepo.getId())) {
372
      existingByQuestionId.put(question.getQuestionId(), question);
373
    }
374
375
    int added = 0;
376
    int updated = 0;
377
    int unchanged = 0;
378
    for (Map.Entry<String, QuestionInfo> entry : foundQuestions.entrySet()) {
379
      String questionId = entry.getKey();
380
      QuestionInfo info = entry.getValue();
381
      PlQuestion existing = existingByQuestionId.get(questionId);
382 1 1. syncQuestions : negated conditional → KILLED
      if (existing == null) {
383
        plQuestionRepository.save(
384
            PlQuestion.builder()
385
                .plRepoId(plRepo.getId())
386
                .questionId(questionId)
387
                .uuid(info.uuid())
388
                .title(info.title())
389
                .build());
390 1 1. syncQuestions : Changed increment from 1 to -1 → KILLED
        added++;
391 1 1. syncQuestions : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log("Added question %s (%s)".formatted(questionId, info.title()));
392 1 1. syncQuestions : negated conditional → KILLED
      } else if (!existing.getUuid().equals(info.uuid())
393 1 1. syncQuestions : negated conditional → KILLED
          || !existing.getTitle().equals(info.title())) {
394 1 1. syncQuestions : removed call to edu/ucsb/cs/scaffold/entity/PlQuestion::setUuid → KILLED
        existing.setUuid(info.uuid());
395 1 1. syncQuestions : removed call to edu/ucsb/cs/scaffold/entity/PlQuestion::setTitle → KILLED
        existing.setTitle(info.title());
396
        plQuestionRepository.save(existing);
397 1 1. syncQuestions : Changed increment from 1 to -1 → KILLED
        updated++;
398 1 1. syncQuestions : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log("Updated question %s (%s)".formatted(questionId, info.title()));
399
      } else {
400 1 1. syncQuestions : Changed increment from 1 to -1 → KILLED
        unchanged++;
401
      }
402
    }
403
404
    int deleted = 0;
405
    List<String> staleQuestionIds =
406
        existingByQuestionId.keySet().stream()
407 2 1. lambda$syncQuestions$2 : replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$syncQuestions$2 → KILLED
2. lambda$syncQuestions$2 : negated conditional → KILLED
            .filter(questionId -> !foundQuestions.containsKey(questionId))
408
            .sorted()
409
            .toList();
410
    for (String questionId : staleQuestionIds) {
411
      PlQuestion stale = existingByQuestionId.get(questionId);
412 1 1. syncQuestions : removed call to edu/ucsb/cs/scaffold/repository/PlScaffoldAssessmentRepository::deleteByPlQuestionId → KILLED
      plScaffoldAssessmentRepository.deleteByPlQuestionId(stale.getId());
413 1 1. syncQuestions : removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::deleteByPlQuestionId → KILLED
      plAssessmentQuestionRepository.deleteByPlQuestionId(stale.getId());
414 1 1. syncQuestions : removed call to edu/ucsb/cs/scaffold/repository/PlQuestionRepository::delete → KILLED
      plQuestionRepository.delete(stale);
415 1 1. syncQuestions : Changed increment from 1 to -1 → KILLED
      deleted++;
416 1 1. syncQuestions : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log("Deleted question %s (no longer on GitHub)".formatted(questionId));
417
    }
418
419 1 1. syncQuestions : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
    ctx.log(
420
        "Questions: %d added, %d updated, %d deleted, %d unchanged"
421
            .formatted(added, updated, deleted, unchanged));
422
  }
423
424
  /** Syncs assessments from GitHub for the course's own instance only. */
425
  private void syncAssessments(JobContext ctx, PlRepo plRepo, PlInstance instance, String token) {
426
    Map<String, PlQuestion> questionsByQuestionId = new LinkedHashMap<>();
427
    for (PlQuestion question : plQuestionRepository.findByPlRepoId(plRepo.getId())) {
428
      questionsByQuestionId.put(question.getQuestionId(), question);
429
    }
430
431
    int added = 0;
432
    int deleted = 0;
433
    int unchanged = 0;
434
    String assessmentsPath =
435
        "%s/%s/%s".formatted(COURSE_INSTANCES_PATH, instance.getShortName(), ASSESSMENTS_DIRECTORY);
436
    List<DirectoryEntry> entries;
437
    try {
438
      entries = githubService.listDirectory(plRepo.getRepoName(), assessmentsPath, token);
439
    } catch (HttpClientErrorException.NotFound e) {
440 1 1. syncAssessments : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
441
          "Instance %s has no %s directory; skipping assessment sync"
442
              .formatted(instance.getShortName(), ASSESSMENTS_DIRECTORY));
443
      return;
444
    }
445
446
    Map<String, PlAssessment> existingByName = new LinkedHashMap<>();
447
    for (PlAssessment assessment :
448
        plAssessmentRepository.findByPlRepoIdAndPlInstanceId(plRepo.getId(), instance.getId())) {
449
      existingByName.put(assessment.getName(), assessment);
450
    }
451
452
    Set<String> foundNames = new LinkedHashSet<>();
453
    for (DirectoryEntry entry : entries) {
454 1 1. syncAssessments : negated conditional → KILLED
      if (!"dir".equals(entry.type())) {
455
        continue;
456
      }
457
      String assessmentPath = assessmentsPath + "/" + entry.name();
458
      boolean hasInfoAssessment =
459
          githubService.listDirectory(plRepo.getRepoName(), assessmentPath, token).stream()
460 3 1. lambda$syncAssessments$3 : replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$syncAssessments$3 → KILLED
2. lambda$syncAssessments$3 : negated conditional → KILLED
3. lambda$syncAssessments$3 : negated conditional → KILLED
              .anyMatch(e -> INFO_ASSESSMENT_JSON.equals(e.name()) && "file".equals(e.type()));
461 1 1. syncAssessments : negated conditional → KILLED
      if (!hasInfoAssessment) {
462
        continue;
463
      }
464
      String content =
465
          githubService.getFileContent(
466
              plRepo.getRepoName(), assessmentPath + "/" + INFO_ASSESSMENT_JSON, token);
467
      JsonNode root;
468
      try {
469
        root = MAPPER.readTree(content);
470
      } catch (Exception e) {
471 1 1. syncAssessments : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log(
472
            "Skipping assessment %s (instance %s): could not parse %s"
473
                .formatted(entry.name(), instance.getShortName(), INFO_ASSESSMENT_JSON));
474
        continue;
475
      }
476
      foundNames.add(entry.name());
477
478
      PlAssessment assessment = existingByName.get(entry.name());
479 1 1. syncAssessments : negated conditional → KILLED
      if (assessment == null) {
480
        assessment =
481
            plAssessmentRepository.save(
482
                PlAssessment.builder()
483
                    .plRepoId(plRepo.getId())
484
                    .plInstanceId(instance.getId())
485
                    .name(entry.name())
486
                    .build());
487 1 1. syncAssessments : Changed increment from 1 to -1 → KILLED
        added++;
488 1 1. syncAssessments : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log(
489
            "Added assessment %s (instance %s)".formatted(entry.name(), instance.getShortName()));
490
      } else {
491 1 1. syncAssessments : Changed increment from 1 to -1 → KILLED
        unchanged++;
492
      }
493
494
      // get("zones") is null when the key is absent; collectQuestionIds treats that as no links
495 1 1. syncAssessments : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncAssessmentQuestions → KILLED
      syncAssessmentQuestions(
496
          ctx, plRepo, instance, assessment, root.get("zones"), questionsByQuestionId);
497
    }
498
499
    List<String> staleNames =
500
        existingByName.keySet().stream()
501 2 1. lambda$syncAssessments$4 : negated conditional → KILLED
2. lambda$syncAssessments$4 : replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$syncAssessments$4 → KILLED
            .filter(name -> !foundNames.contains(name))
502
            .sorted()
503
            .toList();
504
    for (String name : staleNames) {
505
      PlAssessment stale = existingByName.get(name);
506 1 1. syncAssessments : removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::deleteByPlAssessmentId → KILLED
      plAssessmentQuestionRepository.deleteByPlAssessmentId(stale.getId());
507 1 1. syncAssessments : removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentRepository::delete → KILLED
      plAssessmentRepository.delete(stale);
508 1 1. syncAssessments : Changed increment from 1 to -1 → KILLED
      deleted++;
509 1 1. syncAssessments : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
510
          "Deleted assessment %s (instance %s) (no longer on GitHub)"
511
              .formatted(name, instance.getShortName()));
512
    }
513
514 1 1. syncAssessments : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
    ctx.log("Assessments: %d added, %d deleted, %d unchanged".formatted(added, deleted, unchanged));
515
  }
516
517
  /**
518
   * Copies the PrairieLearn-side fields (issue #71) onto the PlAssessment rows of this instance,
519
   * matched by assessment name. A PrairieLearn assessment with no matching repo row (or the
520
   * reverse) is logged and left alone; the GitHub sync is the source of truth for which rows exist.
521
   */
522
  private void enrichAssessmentsFromPrairieLearn(
523
      JobContext ctx, PlRepo plRepo, PlInstance plInstance, String plToken) {
524
    List<PrairieLearnService.AssessmentInfo> plAssessments;
525
    try {
526
      plAssessments = prairieLearnService.getAssessments(plInstance.getNumericId(), plToken);
527
    } catch (HttpClientErrorException e) {
528 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log(
529
          "Could not list assessments from PrairieLearn (HTTP %d); skipping assessment field updates"
530
              .formatted(e.getStatusCode().value()));
531
      return;
532
    }
533
534
    Map<String, PlAssessment> existingByName = new LinkedHashMap<>();
535
    for (PlAssessment assessment :
536
        plAssessmentRepository.findByPlRepoIdAndPlInstanceId(plRepo.getId(), plInstance.getId())) {
537
      existingByName.put(assessment.getName(), assessment);
538
    }
539
540
    int updated = 0;
541
    int unmatched = 0;
542
    for (PrairieLearnService.AssessmentInfo info : plAssessments) {
543
      PlAssessment assessment = existingByName.get(info.assessmentName());
544 1 1. enrichAssessmentsFromPrairieLearn : negated conditional → KILLED
      if (assessment == null) {
545 1 1. enrichAssessmentsFromPrairieLearn : Changed increment from 1 to -1 → KILLED
        unmatched++;
546 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log(
547
            "PrairieLearn assessment %s has no matching assessment directory in the repo; skipping"
548
                .formatted(info.assessmentName()));
549
        continue;
550
      }
551 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentId → KILLED
      assessment.setPlAssessmentId(info.assessmentId());
552 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentNumber → KILLED
      assessment.setPlAssessmentNumber(info.assessmentNumber());
553 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentOrder → KILLED
      assessment.setPlAssessmentOrder(info.assessmentOrderBy());
554 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentTitle → KILLED
      assessment.setPlAssessmentTitle(info.title());
555 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetAbbreviation → KILLED
      assessment.setPlAssessmentSetAbbreviation(info.assessmentSetAbbreviation());
556 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetNumber → KILLED
      assessment.setPlAssessmentSetNumber(info.assessmentSetNumber());
557 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetHeading → KILLED
      assessment.setPlAssessmentSetHeading(info.assessmentSetHeading());
558 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetColor → KILLED
      assessment.setPlAssessmentSetColor(info.assessmentSetColor());
559
      plAssessmentRepository.save(assessment);
560 1 1. enrichAssessmentsFromPrairieLearn : Changed increment from 1 to -1 → KILLED
      updated++;
561 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log("Updated PrairieLearn fields for assessment %s".formatted(info.assessmentName()));
562
    }
563 1 1. enrichAssessmentsFromPrairieLearn : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
    ctx.log(
564
        "PrairieLearn assessment fields: %d updated, %d without a matching repo assessment"
565
            .formatted(updated, unmatched));
566
  }
567
568
  /**
569
   * Rewrites the assessment's question list (join rows, in zone order) to match the ids referenced
570
   * by the zones node of its infoAssessment.json. Ids that don't match any PlQuestion of this repo
571
   * are logged and skipped. If the list already matches, the rows are left untouched.
572
   */
573
  private void syncAssessmentQuestions(
574
      JobContext ctx,
575
      PlRepo plRepo,
576
      PlInstance instance,
577
      PlAssessment assessment,
578
      JsonNode zones,
579
      Map<String, PlQuestion> questionsByQuestionId) {
580
    Set<String> referencedIds = new LinkedHashSet<>();
581 1 1. syncAssessmentQuestions : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::collectQuestionIds → KILLED
    collectQuestionIds(zones, referencedIds);
582
583
    List<Long> desiredQuestionRowIds = new ArrayList<>();
584
    for (String questionId : referencedIds) {
585
      PlQuestion question = questionsByQuestionId.get(questionId);
586 1 1. syncAssessmentQuestions : negated conditional → KILLED
      if (question == null) {
587 1 1. syncAssessmentQuestions : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log(
588
            "Assessment %s (instance %s) references unknown question id %s; skipping that link"
589
                .formatted(assessment.getName(), instance.getShortName(), questionId));
590
        continue;
591
      }
592
      desiredQuestionRowIds.add(question.getId());
593
    }
594
595
    List<Long> currentQuestionRowIds =
596
        plAssessmentQuestionRepository
597
            .findByPlAssessmentIdOrderByOrdinalAsc(assessment.getId())
598
            .stream()
599
            .map(PlAssessmentQuestion::getPlQuestionId)
600
            .toList();
601 1 1. syncAssessmentQuestions : negated conditional → KILLED
    if (currentQuestionRowIds.equals(desiredQuestionRowIds)) {
602
      return;
603
    }
604
605 1 1. syncAssessmentQuestions : removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::deleteByPlAssessmentId → KILLED
    plAssessmentQuestionRepository.deleteByPlAssessmentId(assessment.getId());
606
    // flush so the deletes hit the database before the re-inserts; Hibernate otherwise orders
607
    // inserts first within the transaction, violating the (assessment, question) unique constraint
608 1 1. syncAssessmentQuestions : removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::flush → KILLED
    plAssessmentQuestionRepository.flush();
609 2 1. syncAssessmentQuestions : changed conditional boundary → KILLED
2. syncAssessmentQuestions : negated conditional → KILLED
    for (int i = 0; i < desiredQuestionRowIds.size(); i++) {
610
      plAssessmentQuestionRepository.save(
611
          PlAssessmentQuestion.builder()
612
              .plRepoId(plRepo.getId())
613
              .plAssessmentId(assessment.getId())
614
              .plQuestionId(desiredQuestionRowIds.get(i))
615
              .ordinal(i)
616
              .build());
617
    }
618 1 1. syncAssessmentQuestions : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
    ctx.log(
619
        "Linked %d question(s) to assessment %s (instance %s)"
620
            .formatted(
621
                desiredQuestionRowIds.size(), assessment.getName(), instance.getShortName()));
622
  }
623
624
  /**
625
   * Recursively collects the value of every {@code "id"} key in the JSON tree under the zones node
626
   * of an infoAssessment.json — each one references a question — preserving document order and
627
   * dropping duplicates.
628
   */
629
  static void collectQuestionIds(JsonNode node, Set<String> ids) {
630 1 1. collectQuestionIds : negated conditional → KILLED
    if (node == null) {
631
      return;
632
    }
633 2 1. collectQuestionIds : negated conditional → KILLED
2. collectQuestionIds : negated conditional → KILLED
    if (node.isObject() && node.hasNonNull("id")) {
634
      ids.add(node.get("id").asText());
635
    }
636
    for (JsonNode child : node) {
637 1 1. collectQuestionIds : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::collectQuestionIds → KILLED
      collectQuestionIds(child, ids);
638
    }
639
  }
640
641
  /**
642
   * Recursively walks a directory under {@code questions}. {@code questionId} is the path relative
643
   * to the questions directory ("" for the questions directory itself, which is never a question).
644
   */
645
  private void walkQuestionsDirectory(
646
      JobContext ctx,
647
      PlRepo plRepo,
648
      String token,
649
      String path,
650
      String questionId,
651
      Map<String, QuestionInfo> foundQuestions) {
652
    List<DirectoryEntry> entries = githubService.listDirectory(plRepo.getRepoName(), path, token);
653
654
    boolean hasInfoJson =
655
        entries.stream()
656 3 1. lambda$walkQuestionsDirectory$5 : negated conditional → KILLED
2. lambda$walkQuestionsDirectory$5 : replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$walkQuestionsDirectory$5 → KILLED
3. lambda$walkQuestionsDirectory$5 : negated conditional → KILLED
            .anyMatch(entry -> INFO_JSON.equals(entry.name()) && "file".equals(entry.type()));
657 2 1. walkQuestionsDirectory : negated conditional → KILLED
2. walkQuestionsDirectory : negated conditional → KILLED
    if (hasInfoJson && !questionId.isEmpty()) {
658
      String content =
659
          githubService.getFileContent(plRepo.getRepoName(), path + "/" + INFO_JSON, token);
660
      QuestionInfo info = parseInfoJson(ctx, questionId, content);
661 1 1. walkQuestionsDirectory : negated conditional → KILLED
      if (info != null) {
662
        foundQuestions.put(questionId, info);
663
      }
664
      return; // a question directory's subdirectories belong to the question; don't recurse
665
    }
666
667
    for (DirectoryEntry entry : entries) {
668 1 1. walkQuestionsDirectory : negated conditional → KILLED
      if (!"dir".equals(entry.type())) {
669
        continue;
670
      }
671 1 1. walkQuestionsDirectory : negated conditional → KILLED
      if (DRAFTS_DIRECTORY.equals(entry.name())) {
672 1 1. walkQuestionsDirectory : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log("Skipping directory %s/%s".formatted(path, entry.name()));
673
        continue;
674
      }
675
      String childQuestionId =
676 1 1. walkQuestionsDirectory : negated conditional → KILLED
          questionId.isEmpty() ? entry.name() : questionId + "/" + entry.name();
677 1 1. walkQuestionsDirectory : removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::walkQuestionsDirectory → KILLED
      walkQuestionsDirectory(
678
          ctx, plRepo, token, path + "/" + entry.name(), childQuestionId, foundQuestions);
679
    }
680
  }
681
682
  /** Returns the parsed uuid/title, or null (with a log message) if info.json is unusable. */
683
  private QuestionInfo parseInfoJson(JobContext ctx, String questionId, String content) {
684
    try {
685
      JsonNode node = MAPPER.readTree(content);
686 2 1. parseInfoJson : negated conditional → KILLED
2. parseInfoJson : negated conditional → KILLED
      if (!node.hasNonNull("uuid") || !node.hasNonNull("title")) {
687 1 1. parseInfoJson : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
        ctx.log("Skipping question %s: info.json is missing uuid or title".formatted(questionId));
688
        return null;
689
      }
690 1 1. parseInfoJson : replaced return value with null for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::parseInfoJson → KILLED
      return new QuestionInfo(
691
          UUID.fromString(node.get("uuid").asText()), node.get("title").asText());
692
    } catch (Exception e) {
693 1 1. parseInfoJson : removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED
      ctx.log("Skipping question %s: could not parse info.json".formatted(questionId));
694
      return null;
695
    }
696
  }
697
}

Mutations

112

1.1
Location : getScopeType
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:the_job_reports_its_course_scope_for_the_jobs_table()]
replaced return value with "" for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::getScopeType → KILLED

117

1.1
Location : getScopeId
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:the_job_reports_its_course_scope_for_the_jobs_table()]
replaced Long return value with 0L for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::getScopeId → KILLED

122

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

131

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_course_has_no_instance_association()]
negated conditional → KILLED

2.2
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_course_has_no_instance_association()]
negated conditional → KILLED

133

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_prairielearn_pat_is_missing()]
negated conditional → KILLED

136

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_prairielearn_pat_is_missing()]
negated conditional → KILLED

145

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_course_has_no_instance_association()]
negated conditional → KILLED

2.2
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_plrepo_row_does_not_exist()]
negated conditional → KILLED

147

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_course_has_no_instance_association()]
negated conditional → KILLED

150

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_course_has_no_instance_association()]
negated conditional → KILLED

160

1.1
Location : lambda$accept$0
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_plrepo_row_does_not_exist()]
replaced return value with null for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$accept$0 → KILLED

165

1.1
Location : lambda$accept$1
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_plinstance_row_does_not_exist()]
replaced return value with null for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$accept$1 → KILLED

166

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_instance_has_no_numeric_id()]
negated conditional → KILLED

188

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_the_github_pat_has_read_only_access()]
negated conditional → KILLED

201

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:fails_when_prairielearn_returns_no_data_for_the_instance()]
negated conditional → KILLED

206

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

211

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::sanityCheckInstance → KILLED

214

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncAssessmentSets → KILLED

215

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:other_github_errors_propagate_and_fail_the_job()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncQuestions → KILLED

216

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncAssessments → KILLED

224

1.1
Location : accept
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::enrichAssessmentsFromPrairieLearn → KILLED

235

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
negated conditional → KILLED

236

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_the_stored_names_when_the_instance_was_renamed_on_prairielearn()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

239

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_the_stored_names_when_the_instance_was_renamed_on_prairielearn()]
removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setShortName → KILLED

242

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
negated conditional → KILLED

243

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_the_stored_names_when_the_instance_was_renamed_on_prairielearn()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

246

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_the_stored_names_when_the_instance_was_renamed_on_prairielearn()]
removed call to edu/ucsb/cs/scaffold/entity/PlInstance::setLongName → KILLED

249

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
negated conditional → KILLED

252

1.1
Location : sanityCheckInstance
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

268

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

278

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_sync_when_infoCourse_json_cannot_be_parsed()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

285

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_sync_when_assessmentSets_value_is_not_an_array()]
negated conditional → KILLED

286

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_sync_when_assessmentSets_key_is_missing()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

291

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_sync_when_assessmentSets_value_is_not_an_array()]
negated conditional → KILLED

292

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_sync_when_assessmentSets_value_is_not_an_array()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

313

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_entries_missing_a_heading()]
negated conditional → KILLED

2.2
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_entries_missing_a_name()]
negated conditional → KILLED

3.3
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_entries_missing_an_abbreviation()]
negated conditional → KILLED

4.4
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_entries_missing_a_required_field()]
negated conditional → KILLED

314

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_entries_missing_a_required_field()]
Changed increment from 1 to -1 → KILLED

315

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_entries_missing_a_required_field()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

323

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_an_assessment_set_whose_heading_alone_changed()]
negated conditional → KILLED

332

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_assessment_sets_no_longer_present()]
Changed increment from 1 to -1 → KILLED

333

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:leaves_an_unchanged_assessment_set_untouched()]
negated conditional → KILLED

334

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_an_assessment_set_whose_heading_alone_changed()]
negated conditional → KILLED

335

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:leaves_an_unchanged_assessment_set_untouched()]
negated conditional → KILLED

336

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_a_changed_assessment_set_matched_by_abbreviation()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessmentSet::setName → KILLED

337

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_an_assessment_set_whose_heading_alone_changed()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessmentSet::setHeading → KILLED

338

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_an_assessment_set_whose_color_alone_changed()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessmentSet::setColor → KILLED

340

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_a_changed_assessment_set_matched_by_abbreviation()]
Changed increment from 1 to -1 → KILLED

342

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:leaves_an_unchanged_assessment_set_untouched()]
Changed increment from 1 to -1 → KILLED

348

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_assessment_sets_no_longer_present()]
negated conditional → KILLED

349

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_assessment_sets_no_longer_present()]
removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentSetRepository::delete → KILLED

350

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_assessment_sets_no_longer_present()]
Changed increment from 1 to -1 → KILLED

354

1.1
Location : syncAssessmentSets
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:skips_assessment_set_entries_missing_a_required_field()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

362

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:other_github_errors_propagate_and_fail_the_job()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::walkQuestionsDirectory → KILLED

364

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

382

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_unchanged_question_is_not_saved_again()]
negated conditional → KILLED

390

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:adds_top_level_questions_skipping_drafts_files_and_question_subdirectories()]
Changed increment from 1 to -1 → KILLED

391

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:adds_top_level_questions_skipping_drafts_files_and_question_subdirectories()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

392

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_unchanged_question_is_not_saved_again()]
negated conditional → KILLED

393

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_unchanged_question_is_not_saved_again()]
negated conditional → KILLED

394

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_a_question_whose_uuid_or_title_changed()]
removed call to edu/ucsb/cs/scaffold/entity/PlQuestion::setUuid → KILLED

395

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_a_question_whose_uuid_or_title_changed()]
removed call to edu/ucsb/cs/scaffold/entity/PlQuestion::setTitle → KILLED

397

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_a_question_whose_uuid_or_title_changed()]
Changed increment from 1 to -1 → KILLED

398

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_a_question_whose_uuid_or_title_changed()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

400

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_unchanged_question_is_not_saved_again()]
Changed increment from 1 to -1 → KILLED

407

1.1
Location : lambda$syncQuestions$2
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_unchanged_question_is_not_saved_again()]
replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$syncQuestions$2 → KILLED

2.2
Location : lambda$syncQuestions$2
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_questions_cascading_to_their_scaffold_assessments()]
negated conditional → KILLED

412

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_questions_cascading_to_their_scaffold_assessments()]
removed call to edu/ucsb/cs/scaffold/repository/PlScaffoldAssessmentRepository::deleteByPlQuestionId → KILLED

413

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_questions_cascading_to_their_scaffold_assessments()]
removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::deleteByPlQuestionId → KILLED

414

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_questions_cascading_to_their_scaffold_assessments()]
removed call to edu/ucsb/cs/scaffold/repository/PlQuestionRepository::delete → KILLED

415

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_questions_cascading_to_their_scaffold_assessments()]
Changed increment from 1 to -1 → KILLED

416

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:deletes_stale_questions_cascading_to_their_scaffold_assessments()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

419

1.1
Location : syncQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_subdirectory_named_info_json_does_not_make_its_parent_a_question()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

440

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

454

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_with_unparseable_infoAssessment_json_is_skipped()]
negated conditional → KILLED

460

1.1
Location : lambda$syncAssessments$3
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:directories_without_infoAssessment_json_are_not_assessments()]
replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$syncAssessments$3 → KILLED

2.2
Location : lambda$syncAssessments$3
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:directories_without_infoAssessment_json_are_not_assessments()]
negated conditional → KILLED

3.3
Location : lambda$syncAssessments$3
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:directories_without_infoAssessment_json_are_not_assessments()]
negated conditional → KILLED

461

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:directories_without_infoAssessment_json_are_not_assessments()]
negated conditional → KILLED

471

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_with_unparseable_infoAssessment_json_is_skipped()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

479

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_without_a_zones_key_gets_no_question_links()]
negated conditional → KILLED

487

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_without_a_zones_key_gets_no_question_links()]
Changed increment from 1 to -1 → KILLED

488

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_without_a_zones_key_gets_no_question_links()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

491

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_whose_links_already_match_is_left_untouched()]
Changed increment from 1 to -1 → KILLED

495

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:unknown_question_ids_in_zones_are_logged_and_skipped()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::syncAssessmentQuestions → KILLED

501

1.1
Location : lambda$syncAssessments$4
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:stale_assessments_are_deleted_with_their_join_rows()]
negated conditional → KILLED

2.2
Location : lambda$syncAssessments$4
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_whose_links_already_match_is_left_untouched()]
replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$syncAssessments$4 → KILLED

506

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:stale_assessments_are_deleted_with_their_join_rows()]
removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::deleteByPlAssessmentId → KILLED

507

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:stale_assessments_are_deleted_with_their_join_rows()]
removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentRepository::delete → KILLED

508

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:stale_assessments_are_deleted_with_their_join_rows()]
Changed increment from 1 to -1 → KILLED

509

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:stale_assessments_are_deleted_with_their_join_rows()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

514

1.1
Location : syncAssessments
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:updates_the_stored_names_when_the_instance_was_renamed_on_prairielearn()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

528

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_prairielearn_error_on_the_assessments_listing_skips_enrichment_only()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

544

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:prairielearn_assessments_with_no_matching_repo_row_are_logged_and_skipped()]
negated conditional → KILLED

545

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:prairielearn_assessments_with_no_matching_repo_row_are_logged_and_skipped()]
Changed increment from 1 to -1 → KILLED

546

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:prairielearn_assessments_with_no_matching_repo_row_are_logged_and_skipped()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

551

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentId → KILLED

552

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentNumber → KILLED

553

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentOrder → KILLED

554

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentTitle → KILLED

555

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetAbbreviation → KILLED

556

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetNumber → KILLED

557

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetHeading → KILLED

558

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs/scaffold/entity/PlAssessment::setPlAssessmentSetColor → KILLED

560

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
Changed increment from 1 to -1 → KILLED

561

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:copies_the_prairielearn_fields_onto_the_matching_assessment()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

563

1.1
Location : enrichAssessmentsFromPrairieLearn
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:happy_path_with_nothing_to_sync_verifies_metadata_and_logs_each_step()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

581

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_whose_links_already_match_is_left_untouched()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::collectQuestionIds → KILLED

586

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_whose_links_already_match_is_left_untouched()]
negated conditional → KILLED

587

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:unknown_question_ids_in_zones_are_logged_and_skipped()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

601

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_without_a_zones_key_gets_no_question_links()]
negated conditional → KILLED

605

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:changed_links_are_rewritten_with_a_flush_between_delete_and_insert()]
removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::deleteByPlAssessmentId → KILLED

608

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:changed_links_are_rewritten_with_a_flush_between_delete_and_insert()]
removed call to edu/ucsb/cs/scaffold/repository/PlAssessmentQuestionRepository::flush → KILLED

609

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:unknown_question_ids_in_zones_are_logged_and_skipped()]
changed conditional boundary → KILLED

2.2
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:unknown_question_ids_in_zones_are_logged_and_skipped()]
negated conditional → KILLED

618

1.1
Location : syncAssessmentQuestions
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:unknown_question_ids_in_zones_are_logged_and_skipped()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

630

1.1
Location : collectQuestionIds
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_without_a_zones_key_gets_no_question_links()]
negated conditional → KILLED

633

1.1
Location : collectQuestionIds
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_whose_links_already_match_is_left_untouched()]
negated conditional → KILLED

2.2
Location : collectQuestionIds
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_whose_links_already_match_is_left_untouched()]
negated conditional → KILLED

637

1.1
Location : collectQuestionIds
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_assessment_whose_links_already_match_is_left_untouched()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::collectQuestionIds → KILLED

656

1.1
Location : lambda$walkQuestionsDirectory$5
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_uuid_is_skipped()]
negated conditional → KILLED

2.2
Location : lambda$walkQuestionsDirectory$5
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_subdirectory_named_info_json_does_not_make_its_parent_a_question()]
replaced boolean return with true for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::lambda$walkQuestionsDirectory$5 → KILLED

3.3
Location : lambda$walkQuestionsDirectory$5
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_subdirectory_named_info_json_does_not_make_its_parent_a_question()]
negated conditional → KILLED

657

1.1
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_subdirectory_named_info_json_does_not_make_its_parent_a_question()]
negated conditional → KILLED

2.2
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_uuid_is_skipped()]
negated conditional → KILLED

661

1.1
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_title_is_skipped()]
negated conditional → KILLED

668

1.1
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_uuid_is_skipped()]
negated conditional → KILLED

671

1.1
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_subdirectory_named_info_json_does_not_make_its_parent_a_question()]
negated conditional → KILLED

672

1.1
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:adds_top_level_questions_skipping_drafts_files_and_question_subdirectories()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

676

1.1
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_uuid_is_skipped()]
negated conditional → KILLED

677

1.1
Location : walkQuestionsDirectory
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_uuid_is_skipped()]
removed call to edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::walkQuestionsDirectory → KILLED

686

1.1
Location : parseInfoJson
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_with_an_invalid_uuid_is_skipped()]
negated conditional → KILLED

2.2
Location : parseInfoJson
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_uuid_is_skipped()]
negated conditional → KILLED

687

1.1
Location : parseInfoJson
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_whose_info_json_lacks_uuid_is_skipped()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

690

1.1
Location : parseInfoJson
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:an_unchanged_question_is_not_saved_again()]
replaced return value with null for edu/ucsb/cs/scaffold/jobs/SyncCourseWithPlRepoJob::parseInfoJson → KILLED

693

1.1
Location : parseInfoJson
Killed by : edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.jobs.SyncCourseWithPlRepoJobTests]/[method:a_question_with_an_invalid_uuid_is_skipped()]
removed call to edu/ucsb/cs156/jobs/services/JobContext::log → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0