ConceptYamlService.java

1
package edu.ucsb.cs.scaffold.services;
2
3
import com.fasterxml.jackson.core.JsonProcessingException;
4
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
5
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
6
import edu.ucsb.cs.scaffold.entity.Concept;
7
import edu.ucsb.cs.scaffold.entity.ConceptEdge;
8
import edu.ucsb.cs.scaffold.entity.Course;
9
import edu.ucsb.cs.scaffold.entity.PracticeProblem;
10
import edu.ucsb.cs.scaffold.errors.EntityNotFoundException;
11
import edu.ucsb.cs.scaffold.model.ConceptGraphYamlDTO;
12
import edu.ucsb.cs.scaffold.model.UserState;
13
import edu.ucsb.cs.scaffold.repository.ConceptEdgeRepository;
14
import edu.ucsb.cs.scaffold.repository.ConceptRepository;
15
import edu.ucsb.cs.scaffold.repository.CourseRepository;
16
import edu.ucsb.cs.scaffold.repository.PracticeProblemRepository;
17
import edu.ucsb.cs.scaffold.repository.UserStateRepository;
18
import java.io.IOException;
19
import java.io.InputStream;
20
import java.util.ArrayList;
21
import java.util.Comparator;
22
import java.util.HashMap;
23
import java.util.HashSet;
24
import java.util.LinkedHashMap;
25
import java.util.List;
26
import java.util.Map;
27
import java.util.Set;
28
import java.util.stream.Collectors;
29
import lombok.RequiredArgsConstructor;
30
import org.springframework.stereotype.Service;
31
import org.springframework.transaction.annotation.Transactional;
32
33
/**
34
 * Exports and imports a course's entire concept-graph content (concepts, subconcepts, prerequisite
35
 * edges, and practice problems) as a human-editable YAML document. See docs/yaml-format.md for the
36
 * format specification.
37
 */
38
@Service
39
@RequiredArgsConstructor
40
public class ConceptYamlService {
41
42
  // Emits multi-line Markdown as | block scalars and leaves simple strings unquoted, so the
43
  // export is pleasant to hand-edit. Reading is strict (FAIL_ON_UNKNOWN_PROPERTIES is on by
44
  // default outside Spring): an unknown key in an uploaded file is almost always a typo.
45
  private static final YAMLMapper YAML_MAPPER =
46
      YAMLMapper.builder()
47
          .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES)
48
          .enable(YAMLGenerator.Feature.LITERAL_BLOCK_STYLE)
49
          .enable(YAMLGenerator.Feature.INDENT_ARRAYS_WITH_INDICATOR)
50
          .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)
51
          .build();
52
53
  // The order subconcepts are listed under their parent: the author's ordering, with the same
54
  // id tiebreaker as the /api/concepts/graph endpoint.
55
  private static final Comparator<Concept> SUBCONCEPT_DISPLAY_ORDER =
56
      Comparator.comparing(Concept::getSortOrder, Comparator.nullsLast(Comparator.naturalOrder()))
57
          .thenComparing(Concept::getId);
58
59
  // Matches the practice_problems.url column width.
60
  public static final int MAX_URL_LENGTH = 512;
61
62
  private final ConceptRepository conceptRepository;
63
  private final ConceptEdgeRepository conceptEdgeRepository;
64
  private final PracticeProblemRepository practiceProblemRepository;
65
  private final CourseRepository courseRepository;
66
  private final UserStateRepository userStateRepository;
67
  private final MarkdownService markdownService;
68
  private final ConceptGraphService conceptGraphService;
69
70
  /**
71
   * The complete concept-graph content of the course as a YAML document (see docs/yaml-format.md).
72
   * Top-level concepts are numbered with consecutive external ids 1..n in database-id order; edges
73
   * refer to those external ids.
74
   */
75
  public String createYAML(long courseId) throws EntityNotFoundException, JsonProcessingException {
76
    Course course =
77
        courseRepository
78
            .findById(courseId)
79 1 1. lambda$createYAML$0 : replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$0 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
80
81
    List<Concept> concepts = conceptRepository.findByCourseId(courseId);
82
83
    List<Concept> topLevelConcepts =
84
        concepts.stream()
85 2 1. lambda$createYAML$1 : replaced boolean return with true for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$1 → KILLED
2. lambda$createYAML$1 : negated conditional → KILLED
            .filter(concept -> !concept.isSubconcept())
86
            .sorted(Comparator.comparing(Concept::getId))
87
            .toList();
88
89
    Map<Long, List<Concept>> subconceptsByParentId =
90
        concepts.stream()
91
            .filter(Concept::isSubconcept)
92
            .sorted(SUBCONCEPT_DISPLAY_ORDER)
93
            .collect(
94
                Collectors.groupingBy(
95 1 1. lambda$createYAML$2 : replaced Long return value with 0L for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$2 → KILLED
                    concept -> concept.getParent().getId(), HashMap::new, Collectors.toList()));
96
97
    Map<Long, List<String>> urlsByConceptId = urlsByConceptId(courseId);
98
99
    Map<Long, Long> externalIdByInternalId = new HashMap<>();
100 2 1. createYAML : changed conditional boundary → KILLED
2. createYAML : negated conditional → KILLED
    for (int i = 0; i < topLevelConcepts.size(); i++) {
101 1 1. createYAML : Replaced integer addition with subtraction → KILLED
      externalIdByInternalId.put(topLevelConcepts.get(i).getId(), (long) (i + 1));
102
    }
103
104
    List<ConceptGraphYamlDTO.ConceptNodeDTO> conceptNodes = new ArrayList<>();
105
    for (Concept concept : topLevelConcepts) {
106
      List<ConceptGraphYamlDTO.SubconceptNodeDTO> subconceptNodes =
107
          subconceptsByParentId.getOrDefault(concept.getId(), List.of()).stream()
108
              .map(
109
                  sub ->
110 1 1. lambda$createYAML$3 : replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$3 → KILLED
                      ConceptGraphYamlDTO.SubconceptNodeDTO.builder()
111
                          .label(sub.getLabel())
112
                          .description(sub.getDescription())
113
                          .example(sub.getExample())
114
                          .practiceProblems(urlsByConceptId.get(sub.getId()))
115
                          .build())
116
              .toList();
117
      conceptNodes.add(
118
          ConceptGraphYamlDTO.ConceptNodeDTO.builder()
119
              .id(externalIdByInternalId.get(concept.getId()))
120
              .label(concept.getLabel())
121
              .color(concept.getColor())
122
              .level(concept.getLevel())
123
              .x(concept.getX())
124
              .y(concept.getY())
125
              .description(concept.getDescription())
126
              .example(concept.getExample())
127
              .practiceProblems(urlsByConceptId.get(concept.getId()))
128 1 1. createYAML : negated conditional → KILLED
              .subconcepts(subconceptNodes.isEmpty() ? null : subconceptNodes)
129
              .build());
130
    }
131
132
    List<ConceptGraphYamlDTO.EdgeNodeDTO> edgeNodes =
133
        conceptEdgeRepository.findByCourseId(courseId).stream()
134
            .map(
135
                edge ->
136 1 1. lambda$createYAML$4 : replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$4 → KILLED
                    ConceptGraphYamlDTO.EdgeNodeDTO.builder()
137
                        .from(externalIdByInternalId.get(edge.getSource().getId()))
138
                        .to(externalIdByInternalId.get(edge.getTarget().getId()))
139
                        .build())
140
            .sorted(
141
                Comparator.comparing(ConceptGraphYamlDTO.EdgeNodeDTO::getFrom)
142
                    .thenComparing(ConceptGraphYamlDTO.EdgeNodeDTO::getTo))
143
            .toList();
144
145
    ConceptGraphYamlDTO dto =
146
        ConceptGraphYamlDTO.builder().format(1).concepts(conceptNodes).edges(edgeNodes).build();
147
148
    String header =
149
        "# Concept graph for course %d (%s)%n# See docs/yaml-format.md for the format.%n"
150
            .formatted(course.getId(), course.getCourseName());
151 1 1. createYAML : replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::createYAML → KILLED
    return header + YAML_MAPPER.writeValueAsString(dto);
152
  }
153
154
  /**
155
   * Replaces the course's entire concept-graph content with the content of a YAML document in the
156
   * docs/yaml-format.md format, reporting the outcome as a JSON-shaped map.
157
   *
158
   * <p>The replacement is all-or-nothing: the document is fully parsed and validated first, and if
159
   * anything is wrong the course is left untouched and every problem found is reported under {@code
160
   * errors}. Only a valid document deletes the course's concepts, edges, practice problems, and
161
   * per-user scaffold state ({@code user_state} rows, which refer to concepts by ids that no longer
162
   * exist after a replacement) before creating the new content.
163
   */
164
  @Transactional
165
  public Map<String, Object> replaceFromYAML(long courseId, InputStream yamlStream)
166
      throws EntityNotFoundException {
167
    Course course =
168
        courseRepository
169
            .findById(courseId)
170 1 1. lambda$replaceFromYAML$5 : replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$replaceFromYAML$5 → KILLED
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId));
171
172
    List<String> errors = new ArrayList<>();
173
    ConceptGraphYamlDTO dto = parseYaml(yamlStream, errors);
174 1 1. replaceFromYAML : negated conditional → KILLED
    if (dto != null) {
175 1 1. replaceFromYAML : removed call to edu/ucsb/cs/scaffold/services/ConceptYamlService::validate → KILLED
      validate(dto, errors);
176
    }
177 1 1. replaceFromYAML : negated conditional → KILLED
    if (!errors.isEmpty()) {
178 1 1. replaceFromYAML : replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::replaceFromYAML → KILLED
      return report(false, errors, 0, 0, 0, 0, 0);
179
    }
180
181
    int userStatesCleared = deleteCourseContentAndUserState(courseId);
182
183
    int conceptsCreated = 0;
184
    int subconceptsCreated = 0;
185
    int edgesCreated = 0;
186
    int practiceProblemsCreated = 0;
187
188
    Map<Long, Concept> savedByExternalId = new HashMap<>();
189
    for (ConceptGraphYamlDTO.ConceptNodeDTO node : dto.getConcepts()) {
190 1 1. replaceFromYAML : negated conditional → KILLED
      int level = node.getLevel() != null ? node.getLevel() : 1;
191
      Concept saved =
192
          conceptRepository.save(
193
              Concept.builder()
194
                  .course(course)
195
                  .label(markdownService.clean(node.getLabel()))
196
                  .description(markdownService.clean(node.getDescription()))
197
                  .example(markdownService.clean(node.getExample()))
198
                  .color(
199 1 1. replaceFromYAML : negated conditional → KILLED
                      node.getColor() != null
200
                          ? node.getColor()
201
                          : conceptGraphService.colorForLevel(level))
202
                  .level(level)
203 1 1. replaceFromYAML : negated conditional → KILLED
                  .x(node.getX() != null ? node.getX() : 0)
204 1 1. replaceFromYAML : negated conditional → KILLED
                  .y(node.getY() != null ? node.getY() : 0)
205
                  .build());
206
      savedByExternalId.put(node.getId(), saved);
207 1 1. replaceFromYAML : Changed increment from 1 to -1 → KILLED
      conceptsCreated++;
208 1 1. replaceFromYAML : Replaced integer addition with subtraction → KILLED
      practiceProblemsCreated += savePracticeProblems(course, saved, node.getPracticeProblems());
209
210
      List<ConceptGraphYamlDTO.SubconceptNodeDTO> subconcepts =
211 1 1. replaceFromYAML : negated conditional → KILLED
          node.getSubconcepts() != null ? node.getSubconcepts() : List.of();
212 2 1. replaceFromYAML : changed conditional boundary → KILLED
2. replaceFromYAML : negated conditional → KILLED
      for (int i = 0; i < subconcepts.size(); i++) {
213
        ConceptGraphYamlDTO.SubconceptNodeDTO subNode = subconcepts.get(i);
214
        Concept savedSub =
215
            conceptRepository.save(
216
                Concept.builder()
217
                    .course(course)
218
                    .label(markdownService.clean(subNode.getLabel()))
219
                    .description(markdownService.clean(subNode.getDescription()))
220
                    .example(markdownService.clean(subNode.getExample()))
221
                    .parent(saved)
222
                    .sortOrder(i)
223
                    .build());
224 1 1. replaceFromYAML : Changed increment from 1 to -1 → KILLED
        subconceptsCreated++;
225
        practiceProblemsCreated +=
226 1 1. replaceFromYAML : Replaced integer addition with subtraction → KILLED
            savePracticeProblems(course, savedSub, subNode.getPracticeProblems());
227
      }
228
    }
229
230
    for (ConceptGraphYamlDTO.EdgeNodeDTO edgeNode :
231 1 1. replaceFromYAML : negated conditional → KILLED
        dto.getEdges() != null ? dto.getEdges() : List.<ConceptGraphYamlDTO.EdgeNodeDTO>of()) {
232
      conceptEdgeRepository.save(
233
          ConceptEdge.builder()
234
              .course(course)
235
              .source(savedByExternalId.get(edgeNode.getFrom()))
236
              .target(savedByExternalId.get(edgeNode.getTo()))
237
              .build());
238 1 1. replaceFromYAML : Changed increment from 1 to -1 → KILLED
      edgesCreated++;
239
    }
240
241 1 1. replaceFromYAML : replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::replaceFromYAML → KILLED
    return report(
242
        true,
243
        errors,
244
        conceptsCreated,
245
        subconceptsCreated,
246
        edgesCreated,
247
        practiceProblemsCreated,
248
        userStatesCleared);
249
  }
250
251
  private ConceptGraphYamlDTO parseYaml(InputStream yamlStream, List<String> errors) {
252
    // Read the stream ourselves before handing the text to Jackson: Jackson wraps stream
253
    // IOExceptions in JsonProcessingException, which would make an unreadable upload look
254
    // like a syntax error in the file.
255
    try {
256
      String yaml = new String(yamlStream.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
257 1 1. parseYaml : replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::parseYaml → KILLED
      return YAML_MAPPER.readValue(yaml, ConceptGraphYamlDTO.class);
258
    } catch (JsonProcessingException e) {
259
      errors.add("could not parse YAML: " + e.getOriginalMessage());
260
    } catch (IOException e) {
261
      errors.add("could not read file: " + e.getMessage());
262
    }
263
    return null;
264
  }
265
266
  /**
267
   * Checks everything that could make the document unloadable or leave the course in a state the
268
   * rest of the app does not expect (the same invariants the single-concept endpoints enforce).
269
   * Every problem found is appended to {@code errors}; an empty list afterward means the document
270
   * is safe to load.
271
   */
272
  private void validate(ConceptGraphYamlDTO dto, List<String> errors) {
273 1 1. validate : negated conditional → KILLED
    if (dto.getFormat() == null) {
274
      errors.add("format is required (expected: format: 1)");
275 1 1. validate : negated conditional → KILLED
    } else if (dto.getFormat() != 1) {
276
      errors.add("unsupported format %d (expected: format: 1)".formatted(dto.getFormat()));
277
    }
278 1 1. validate : negated conditional → KILLED
    if (dto.getConcepts() == null) {
279
      errors.add("concepts is required (may be an empty list: concepts: [])");
280
    }
281
282
    Set<Long> externalIds = new HashSet<>();
283
    List<ConceptGraphYamlDTO.ConceptNodeDTO> concepts =
284 1 1. validate : negated conditional → KILLED
        dto.getConcepts() != null ? dto.getConcepts() : List.of();
285 2 1. validate : negated conditional → KILLED
2. validate : changed conditional boundary → KILLED
    for (int i = 0; i < concepts.size(); i++) {
286
      ConceptGraphYamlDTO.ConceptNodeDTO node = concepts.get(i);
287
      String where = "concepts[%d]".formatted(i);
288 1 1. validate : negated conditional → KILLED
      if (node == null) {
289
        errors.add(where + " is empty");
290
        continue;
291
      }
292 1 1. validate : negated conditional → KILLED
      if (node.getId() == null) {
293
        errors.add(where + ": id is required");
294 1 1. validate : negated conditional → KILLED
      } else if (!externalIds.add(node.getId())) {
295
        errors.add(where + ": duplicate id " + node.getId());
296
      }
297
      validateLabel(where, node.getLabel(), errors, Concept.MAX_RENDERED_LABEL_LENGTH);
298 1 1. validate : removed call to edu/ucsb/cs/scaffold/services/ConceptYamlService::validateUrls → KILLED
      validateUrls(where, node.getPracticeProblems(), errors);
299
300
      Set<String> subconceptLabels = new HashSet<>();
301
      List<ConceptGraphYamlDTO.SubconceptNodeDTO> subconcepts =
302 1 1. validate : negated conditional → KILLED
          node.getSubconcepts() != null ? node.getSubconcepts() : List.of();
303 2 1. validate : negated conditional → KILLED
2. validate : changed conditional boundary → KILLED
      for (int j = 0; j < subconcepts.size(); j++) {
304
        ConceptGraphYamlDTO.SubconceptNodeDTO subNode = subconcepts.get(j);
305
        String subWhere = where + ".subconcepts[%d]".formatted(j);
306 1 1. validate : negated conditional → KILLED
        if (subNode == null) {
307
          errors.add(subWhere + " is empty");
308
          continue;
309
        }
310
        String cleanLabel =
311
            validateLabel(
312
                subWhere, subNode.getLabel(), errors, Concept.MAX_RENDERED_SUBCONCEPT_LABEL_LENGTH);
313 2 1. validate : negated conditional → KILLED
2. validate : negated conditional → KILLED
        if (cleanLabel != null && !subconceptLabels.add(cleanLabel)) {
314
          errors.add(subWhere + ": duplicate subconcept label " + cleanLabel);
315
        }
316 1 1. validate : removed call to edu/ucsb/cs/scaffold/services/ConceptYamlService::validateUrls → KILLED
        validateUrls(subWhere, subNode.getPracticeProblems(), errors);
317
      }
318
    }
319
320
    // Edges are checked in order against the edges accepted so far, so the cycle message points
321
    // at the edge that closes the cycle.
322
    List<ConceptEdge> acceptedEdges = new ArrayList<>();
323
    Set<List<Long>> seenEndpoints = new HashSet<>();
324
    List<ConceptGraphYamlDTO.EdgeNodeDTO> edges =
325 1 1. validate : negated conditional → KILLED
        dto.getEdges() != null ? dto.getEdges() : List.of();
326 2 1. validate : changed conditional boundary → KILLED
2. validate : negated conditional → KILLED
    for (int i = 0; i < edges.size(); i++) {
327
      ConceptGraphYamlDTO.EdgeNodeDTO edge = edges.get(i);
328
      String where = "edges[%d]".formatted(i);
329 1 1. validate : negated conditional → KILLED
      if (edge == null) {
330
        errors.add(where + " is empty");
331
        continue;
332
      }
333 2 1. validate : negated conditional → KILLED
2. validate : negated conditional → KILLED
      if (edge.getFrom() == null || edge.getTo() == null) {
334
        errors.add(where + ": from and to are required");
335
        continue;
336
      }
337
      boolean endpointsExist = true;
338 1 1. validate : negated conditional → KILLED
      if (!externalIds.contains(edge.getFrom())) {
339
        errors.add(where + ": no concept with id " + edge.getFrom());
340
        endpointsExist = false;
341
      }
342 1 1. validate : negated conditional → KILLED
      if (!externalIds.contains(edge.getTo())) {
343
        errors.add(where + ": no concept with id " + edge.getTo());
344
        endpointsExist = false;
345
      }
346 1 1. validate : negated conditional → KILLED
      if (!endpointsExist) {
347
        continue;
348
      }
349 1 1. validate : negated conditional → KILLED
      if (edge.getFrom().equals(edge.getTo())) {
350
        errors.add(where + ": an edge cannot connect a concept to itself");
351
        continue;
352
      }
353 1 1. validate : negated conditional → KILLED
      if (!seenEndpoints.add(List.of(edge.getFrom(), edge.getTo()))) {
354
        errors.add(
355
            where + ": duplicate edge from %d to %d".formatted(edge.getFrom(), edge.getTo()));
356
        continue;
357
      }
358 1 1. validate : negated conditional → KILLED
      if (conceptGraphService.wouldCreateCycle(acceptedEdges, edge.getFrom(), edge.getTo())) {
359
        errors.add(
360
            where
361
                + ": edge from %d to %d would create a cycle"
362
                    .formatted(edge.getFrom(), edge.getTo()));
363
        continue;
364
      }
365
      acceptedEdges.add(
366
          ConceptEdge.builder()
367
              .source(Concept.builder().id(edge.getFrom()).build())
368
              .target(Concept.builder().id(edge.getTo()).build())
369
              .build());
370
    }
371
  }
372
373
  /**
374
   * Validates a concept or subconcept label the same way the single-concept endpoints do, and
375
   * returns the cleaned label (used for duplicate detection), or null if it was invalid.
376
   */
377
  private String validateLabel(
378
      String where, String label, List<String> errors, int maxRenderedLength) {
379
    String cleanLabel = markdownService.clean(label);
380 2 1. validateLabel : negated conditional → KILLED
2. validateLabel : negated conditional → KILLED
    if (cleanLabel == null || cleanLabel.isEmpty()) {
381
      errors.add(where + ": label may not be empty");
382 1 1. validateLabel : replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::validateLabel → KILLED
      return null;
383
    }
384
    int renderedLength = markdownService.renderedLength(cleanLabel);
385 2 1. validateLabel : changed conditional boundary → KILLED
2. validateLabel : negated conditional → KILLED
    if (renderedLength > maxRenderedLength) {
386
      errors.add(
387
          where
388
              + ": label renders to %d characters; the maximum is %d"
389
                  .formatted(renderedLength, maxRenderedLength));
390 1 1. validateLabel : replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::validateLabel → KILLED
      return null;
391
    }
392 1 1. validateLabel : replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::validateLabel → KILLED
    return cleanLabel;
393
  }
394
395
  private void validateUrls(String where, List<String> urls, List<String> errors) {
396 1 1. validateUrls : negated conditional → KILLED
    if (urls == null) {
397
      return;
398
    }
399
    Set<String> seen = new HashSet<>();
400 2 1. validateUrls : negated conditional → KILLED
2. validateUrls : changed conditional boundary → KILLED
    for (int i = 0; i < urls.size(); i++) {
401
      String url = urls.get(i);
402
      String urlWhere = where + ".practiceProblems[%d]".formatted(i);
403 2 1. validateUrls : negated conditional → KILLED
2. validateUrls : negated conditional → KILLED
      if (url == null || url.isBlank()) {
404
        errors.add(urlWhere + ": url may not be empty");
405
        continue;
406
      }
407
      String cleanUrl = url.strip();
408 2 1. validateUrls : changed conditional boundary → KILLED
2. validateUrls : negated conditional → KILLED
      if (cleanUrl.length() > MAX_URL_LENGTH) {
409
        errors.add(
410
            urlWhere
411
                + ": url is %d characters long; the maximum is %d"
412
                    .formatted(cleanUrl.length(), MAX_URL_LENGTH));
413
        continue;
414
      }
415 1 1. validateUrls : negated conditional → KILLED
      if (!seen.add(cleanUrl)) {
416
        errors.add(urlWhere + ": duplicate practice problem url " + cleanUrl);
417
      }
418
    }
419
  }
420
421
  /**
422
   * Deletes the course's practice problems, edges, and concepts (children before parents, in an
423
   * order that never breaks a foreign key), plus every user's per-course scaffold state. Returns
424
   * how many user-state rows were cleared.
425
   */
426
  private int deleteCourseContentAndUserState(long courseId) {
427 1 1. deleteCourseContentAndUserState : removed call to edu/ucsb/cs/scaffold/repository/PracticeProblemRepository::deleteAll → KILLED
    practiceProblemRepository.deleteAll(practiceProblemRepository.findByCourseId(courseId));
428 1 1. deleteCourseContentAndUserState : removed call to edu/ucsb/cs/scaffold/repository/ConceptEdgeRepository::deleteAll → KILLED
    conceptEdgeRepository.deleteAll(conceptEdgeRepository.findByCourseId(courseId));
429
430
    List<Concept> existingConcepts = conceptRepository.findByCourseId(courseId);
431 1 1. deleteCourseContentAndUserState : removed call to edu/ucsb/cs/scaffold/repository/ConceptRepository::deleteAll → KILLED
    conceptRepository.deleteAll(existingConcepts.stream().filter(Concept::isSubconcept).toList());
432 1 1. deleteCourseContentAndUserState : removed call to edu/ucsb/cs/scaffold/repository/ConceptRepository::deleteAll → KILLED
    conceptRepository.deleteAll(
433 2 1. lambda$deleteCourseContentAndUserState$6 : replaced boolean return with true for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$deleteCourseContentAndUserState$6 → KILLED
2. lambda$deleteCourseContentAndUserState$6 : negated conditional → KILLED
        existingConcepts.stream().filter(concept -> !concept.isSubconcept()).toList());
434
435
    List<UserState> userStates = userStateRepository.findByCourseId(courseId);
436 1 1. deleteCourseContentAndUserState : removed call to edu/ucsb/cs/scaffold/repository/UserStateRepository::deleteAll → KILLED
    userStateRepository.deleteAll(userStates);
437 1 1. deleteCourseContentAndUserState : replaced int return with 0 for edu/ucsb/cs/scaffold/services/ConceptYamlService::deleteCourseContentAndUserState → KILLED
    return userStates.size();
438
  }
439
440
  private int savePracticeProblems(Course course, Concept concept, List<String> urls) {
441 1 1. savePracticeProblems : negated conditional → KILLED
    if (urls == null) {
442
      return 0;
443
    }
444
    for (String url : urls) {
445
      practiceProblemRepository.save(
446
          PracticeProblem.builder().course(course).concept(concept).url(url.strip()).build());
447
    }
448 1 1. savePracticeProblems : replaced int return with 0 for edu/ucsb/cs/scaffold/services/ConceptYamlService::savePracticeProblems → KILLED
    return urls.size();
449
  }
450
451
  private Map<String, Object> report(
452
      boolean success,
453
      List<String> errors,
454
      int conceptsCreated,
455
      int subconceptsCreated,
456
      int edgesCreated,
457
      int practiceProblemsCreated,
458
      int userStatesCleared) {
459
    Map<String, Object> report = new LinkedHashMap<>();
460
    report.put("success", success);
461
    report.put("errors", errors);
462
    report.put("conceptsCreated", conceptsCreated);
463
    report.put("subconceptsCreated", subconceptsCreated);
464
    report.put("edgesCreated", edgesCreated);
465
    report.put("practiceProblemsCreated", practiceProblemsCreated);
466
    report.put("userStatesCleared", userStatesCleared);
467 1 1. report : replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::report → KILLED
    return report;
468
  }
469
470
  /**
471
   * Every practice problem URL in the course, grouped by concept id, each concept's URLs in
472
   * database-id order (the order they were added). Concepts with no practice problems are absent,
473
   * so lookups feed straight into the NON_EMPTY-serialized DTO fields.
474
   */
475
  private Map<Long, List<String>> urlsByConceptId(long courseId) {
476
    Map<Long, List<String>> result = new HashMap<>();
477
    practiceProblemRepository.findByCourseId(courseId).stream()
478
        .sorted(Comparator.comparing(PracticeProblem::getId))
479 1 1. urlsByConceptId : removed call to java/util/stream/Stream::forEach → KILLED
        .forEach(
480
            problem ->
481
                result
482 1 1. lambda$urlsByConceptId$7 : replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$urlsByConceptId$7 → KILLED
                    .computeIfAbsent(problem.getConcept().getId(), k -> new ArrayList<>())
483
                    .add(problem.getUrl()));
484 1 1. urlsByConceptId : replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::urlsByConceptId → KILLED
    return result;
485
  }
486
}

Mutations

79

1.1
Location : lambda$createYAML$0
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_throws_when_course_not_found()]
replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$0 → KILLED

85

1.1
Location : lambda$createYAML$1
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
replaced boolean return with true for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$1 → KILLED

2.2
Location : lambda$createYAML$1
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
negated conditional → KILLED

95

1.1
Location : lambda$createYAML$2
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
replaced Long return value with 0L for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$2 → KILLED

100

1.1
Location : createYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_an_empty_course()]
changed conditional boundary → KILLED

2.2
Location : createYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_an_empty_course()]
negated conditional → KILLED

101

1.1
Location : createYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
Replaced integer addition with subtraction → KILLED

110

1.1
Location : lambda$createYAML$3
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$3 → KILLED

128

1.1
Location : createYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
negated conditional → KILLED

136

1.1
Location : lambda$createYAML$4
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$createYAML$4 → KILLED

151

1.1
Location : createYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_an_empty_course()]
replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::createYAML → KILLED

170

1.1
Location : lambda$replaceFromYAML$5
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_throws_when_course_not_found()]
replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$replaceFromYAML$5 → KILLED

174

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_unreadable_stream()]
negated conditional → KILLED

175

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
removed call to edu/ucsb/cs/scaffold/services/ConceptYamlService::validate → KILLED

177

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_unreadable_stream()]
negated conditional → KILLED

178

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_unreadable_stream()]
replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::replaceFromYAML → KILLED

190

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
negated conditional → KILLED

199

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
negated conditional → KILLED

203

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
negated conditional → KILLED

204

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
negated conditional → KILLED

207

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
Changed increment from 1 to -1 → KILLED

208

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
Replaced integer addition with subtraction → KILLED

211

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
negated conditional → KILLED

212

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
changed conditional boundary → KILLED

2.2
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
negated conditional → KILLED

224

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
Changed increment from 1 to -1 → KILLED

226

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
Replaced integer addition with subtraction → KILLED

231

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_can_clear_a_course_with_an_empty_concept_list()]
negated conditional → KILLED

238

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
Changed increment from 1 to -1 → KILLED

241

1.1
Location : replaceFromYAML
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_can_clear_a_course_with_an_empty_concept_list()]
replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::replaceFromYAML → KILLED

257

1.1
Location : parseYaml
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
replaced return value with null for edu/ucsb/cs/scaffold/services/ConceptYamlService::parseYaml → KILLED

273

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
negated conditional → KILLED

275

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
negated conditional → KILLED

278

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
negated conditional → KILLED

284

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
negated conditional → KILLED

285

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
negated conditional → KILLED

2.2
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
changed conditional boundary → KILLED

288

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

292

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

294

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

298

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
removed call to edu/ucsb/cs/scaffold/services/ConceptYamlService::validateUrls → KILLED

302

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

303

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

2.2
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
changed conditional boundary → KILLED

306

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
negated conditional → KILLED

313

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_subconcept_problems()]
negated conditional → KILLED

2.2
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
negated conditional → KILLED

316

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
removed call to edu/ucsb/cs/scaffold/services/ConceptYamlService::validateUrls → KILLED

325

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
negated conditional → KILLED

326

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
changed conditional boundary → KILLED

2.2
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_missing_concepts_list()]
negated conditional → KILLED

329

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

333

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

2.2
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

338

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

342

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

346

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

349

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

353

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

358

1.1
Location : validate
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_edge_problems()]
negated conditional → KILLED

380

1.1
Location : validateLabel
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

2.2
Location : validateLabel
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

382

1.1
Location : validateLabel
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_subconcept_problems()]
replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::validateLabel → KILLED

385

1.1
Location : validateLabel
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
changed conditional boundary → KILLED

2.2
Location : validateLabel
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

390

1.1
Location : validateLabel
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_subconcept_problems()]
replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::validateLabel → KILLED

392

1.1
Location : validateLabel
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_subconcept_problems()]
replaced return value with "" for edu/ucsb/cs/scaffold/services/ConceptYamlService::validateLabel → KILLED

396

1.1
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_concept_id_and_label_problems()]
negated conditional → KILLED

400

1.1
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
negated conditional → KILLED

2.2
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
changed conditional boundary → KILLED

403

1.1
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
negated conditional → KILLED

2.2
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
negated conditional → KILLED

408

1.1
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
changed conditional boundary → KILLED

2.2
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
negated conditional → KILLED

415

1.1
Location : validateUrls
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_practice_problem_url_problems()]
negated conditional → KILLED

427

1.1
Location : deleteCourseContentAndUserState
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
removed call to edu/ucsb/cs/scaffold/repository/PracticeProblemRepository::deleteAll → KILLED

428

1.1
Location : deleteCourseContentAndUserState
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
removed call to edu/ucsb/cs/scaffold/repository/ConceptEdgeRepository::deleteAll → KILLED

431

1.1
Location : deleteCourseContentAndUserState
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
removed call to edu/ucsb/cs/scaffold/repository/ConceptRepository::deleteAll → KILLED

432

1.1
Location : deleteCourseContentAndUserState
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
removed call to edu/ucsb/cs/scaffold/repository/ConceptRepository::deleteAll → KILLED

433

1.1
Location : lambda$deleteCourseContentAndUserState$6
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
replaced boolean return with true for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$deleteCourseContentAndUserState$6 → KILLED

2.2
Location : lambda$deleteCourseContentAndUserState$6
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
negated conditional → KILLED

436

1.1
Location : deleteCourseContentAndUserState
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
removed call to edu/ucsb/cs/scaffold/repository/UserStateRepository::deleteAll → KILLED

437

1.1
Location : deleteCourseContentAndUserState
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
replaced int return with 0 for edu/ucsb/cs/scaffold/services/ConceptYamlService::deleteCourseContentAndUserState → KILLED

441

1.1
Location : savePracticeProblems
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_with_defaulted_level_uses_that_levels_color()]
negated conditional → KILLED

448

1.1
Location : savePracticeProblems
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_replaces_existing_content_and_clears_user_state()]
replaced int return with 0 for edu/ucsb/cs/scaffold/services/ConceptYamlService::savePracticeProblems → KILLED

467

1.1
Location : report
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:replaceFromYAML_reports_unreadable_stream()]
replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::report → KILLED

479

1.1
Location : urlsByConceptId
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
removed call to java/util/stream/Stream::forEach → KILLED

482

1.1
Location : lambda$urlsByConceptId$7
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
replaced return value with Collections.emptyList for edu/ucsb/cs/scaffold/services/ConceptYamlService::lambda$urlsByConceptId$7 → KILLED

484

1.1
Location : urlsByConceptId
Killed by : edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests.[engine:junit-jupiter]/[class:edu.ucsb.cs.scaffold.services.ConceptYamlServiceTests]/[method:createYAML_exports_concepts_subconcepts_practice_problems_and_edges()]
replaced return value with Collections.emptyMap for edu/ucsb/cs/scaffold/services/ConceptYamlService::urlsByConceptId → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0