StatisticsController.java

1
package edu.ucsb.cs156.dining.controllers;
2
3
import edu.ucsb.cs156.dining.repositories.ReviewRepository;
4
import edu.ucsb.cs156.dining.repositories.projections.CommonsRatingProjection;
5
import edu.ucsb.cs156.dining.repositories.projections.ItemRatingProjection;
6
import edu.ucsb.cs156.dining.statuses.ModerationStatus;
7
import edu.ucsb.cs156.dining.util.StatsWindow;
8
import io.swagger.v3.oas.annotations.Operation;
9
import io.swagger.v3.oas.annotations.tags.Tag;
10
import java.time.LocalDateTime;
11
import java.util.List;
12
import lombok.extern.slf4j.Slf4j;
13
import org.springframework.beans.factory.annotation.Autowired;
14
import org.springframework.data.domain.PageRequest;
15
import org.springframework.security.access.prepost.PreAuthorize;
16
import org.springframework.web.bind.annotation.GetMapping;
17
import org.springframework.web.bind.annotation.RequestMapping;
18
import org.springframework.web.bind.annotation.RequestParam;
19
import org.springframework.web.bind.annotation.RestController;
20
21
@Tag(name = "Statistics")
22
@RequestMapping("/api/statistics")
23
@RestController
24
@Slf4j
25
public class StatisticsController extends ApiController {
26
27
  private static final int DEFAULT_LIMIT = 10;
28
  private static final int MAX_LIMIT = 50;
29
  private static final long DEFAULT_MIN_REVIEWS = 3;
30
31
  @Autowired ReviewRepository reviewRepository;
32
33
  public record StatisticsSummary(
34
      long totalApprovedReviews,
35
      long totalMenuItemsReviewed,
36
      long totalCommonsCovered,
37
      LocalDateTime lastUpdated) {}
38
39
  public record RatedItem(
40
      Long itemId,
41
      String name,
42
      String diningCommonsCode,
43
      String mealCode,
44
      Double avgStars,
45
      Long reviewCount) {}
46
47
  public record CommonsAverage(String diningCommonsCode, Double avgStars, Long reviewCount) {}
48
49
  @Operation(summary = "Get a summary of review statistics")
50
  @PreAuthorize("hasRole('ROLE_USER')")
51
  @GetMapping(value = "", produces = "application/json")
52
  public StatisticsSummary getSummary() {
53 1 1. getSummary : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::getSummary → KILLED
    return new StatisticsSummary(
54
        reviewRepository.countByStatus(ModerationStatus.APPROVED),
55
        reviewRepository.countDistinctItemsByStatus(ModerationStatus.APPROVED),
56
        reviewRepository.countDistinctCommonsByStatus(ModerationStatus.APPROVED),
57
        reviewRepository.findMaxDateEditedByStatus(ModerationStatus.APPROVED));
58
  }
59
60
  LocalDateTime sinceFor(StatsWindow window) {
61 1 1. sinceFor : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::sinceFor → KILLED
    return window.since(LocalDateTime.now());
62
  }
63
64
  private static int clampLimit(int limit) {
65 1 1. clampLimit : replaced int return with 0 for edu/ucsb/cs156/dining/controllers/StatisticsController::clampLimit → KILLED
    return Math.min(Math.max(limit, 1), MAX_LIMIT);
66
  }
67
68
  private static long clampMinReviews(long minReviews) {
69 1 1. clampMinReviews : replaced long return with 0 for edu/ucsb/cs156/dining/controllers/StatisticsController::clampMinReviews → KILLED
    return Math.max(minReviews, 1L);
70
  }
71
72
  private RatedItem toRatedItem(ItemRatingProjection p) {
73 1 1. toRatedItem : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::toRatedItem → KILLED
    return new RatedItem(
74
        p.getItemId(),
75
        p.getName(),
76
        p.getDiningCommonsCode(),
77
        p.getMealCode(),
78
        p.getAvgStars(),
79
        p.getReviewCount());
80
  }
81
82
  private CommonsAverage toCommonsAverage(CommonsRatingProjection p) {
83 1 1. toCommonsAverage : replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::toCommonsAverage → KILLED
    return new CommonsAverage(p.getDiningCommonsCode(), p.getAvgStars(), p.getReviewCount());
84
  }
85
86
  @Operation(summary = "Get best rated menu items by average stars")
87
  @PreAuthorize("hasRole('ROLE_USER')")
88
  @GetMapping(value = "/items/best", produces = "application/json")
89
  public List<RatedItem> bestRatedItems(
90
      @RequestParam(defaultValue = "ALL") StatsWindow window,
91
      @RequestParam(defaultValue = "" + DEFAULT_LIMIT) int limit,
92
      @RequestParam(name = "minReviews", defaultValue = "" + DEFAULT_MIN_REVIEWS) long minReviews) {
93
    int clampedLimit = clampLimit(limit);
94
    long clampedMinReviews = clampMinReviews(minReviews);
95
96
    LocalDateTime since = sinceFor(window);
97
    List<ItemRatingProjection> projections =
98
        reviewRepository.findTopRatedItems(
99
            ModerationStatus.APPROVED, since, clampedMinReviews, PageRequest.of(0, clampedLimit));
100
101 1 1. bestRatedItems : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::bestRatedItems → KILLED
    return projections.stream().map(this::toRatedItem).toList();
102
  }
103
104
  @Operation(summary = "Get worst rated menu items by average stars")
105
  @PreAuthorize("hasRole('ROLE_USER')")
106
  @GetMapping(value = "/items/worst", produces = "application/json")
107
  public List<RatedItem> worstRatedItems(
108
      @RequestParam(defaultValue = "ALL") StatsWindow window,
109
      @RequestParam(defaultValue = "" + DEFAULT_LIMIT) int limit,
110
      @RequestParam(name = "minReviews", defaultValue = "" + DEFAULT_MIN_REVIEWS) long minReviews) {
111
    int clampedLimit = clampLimit(limit);
112
    long clampedMinReviews = clampMinReviews(minReviews);
113
114
    LocalDateTime since = sinceFor(window);
115
    List<ItemRatingProjection> projections =
116
        reviewRepository.findBottomRatedItems(
117
            ModerationStatus.APPROVED, since, clampedMinReviews, PageRequest.of(0, clampedLimit));
118
119 1 1. worstRatedItems : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::worstRatedItems → KILLED
    return projections.stream().map(this::toRatedItem).toList();
120
  }
121
122
  @Operation(summary = "Get average star ratings grouped by dining commons")
123
  @PreAuthorize("hasRole('ROLE_USER')")
124
  @GetMapping(value = "/commons/averages", produces = "application/json")
125
  public List<CommonsAverage> commonsAverages(
126
      @RequestParam(defaultValue = "ALL") StatsWindow window) {
127
    LocalDateTime since = sinceFor(window);
128
    List<CommonsRatingProjection> projections =
129
        reviewRepository.findCommonsAverages(ModerationStatus.APPROVED, since);
130
131 1 1. commonsAverages : replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::commonsAverages → KILLED
    return projections.stream().map(this::toCommonsAverage).toList();
132
  }
133
}

Mutations

53

1.1
Location : getSummary
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:api_statistics_user_logged_in_returns_expected_shape()]
replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::getSummary → KILLED

61

1.1
Location : sinceFor
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:items_worst_window_ONE_WEEK_passes_correct_since()]
replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::sinceFor → KILLED

65

1.1
Location : clampLimit
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:items_best_limit_above_max_is_clamped_to_50()]
replaced int return with 0 for edu/ucsb/cs156/dining/controllers/StatisticsController::clampLimit → KILLED

69

1.1
Location : clampMinReviews
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:items_best_limit_above_max_is_clamped_to_50()]
replaced long return with 0 for edu/ucsb/cs156/dining/controllers/StatisticsController::clampMinReviews → KILLED

73

1.1
Location : toRatedItem
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:items_best_default_params_uses_ALL_window_limit_10_minReviews_3()]
replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::toRatedItem → KILLED

83

1.1
Location : toCommonsAverage
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:commons_averages_default_window_ALL_returns_expected_shape()]
replaced return value with null for edu/ucsb/cs156/dining/controllers/StatisticsController::toCommonsAverage → KILLED

101

1.1
Location : bestRatedItems
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:items_best_default_params_uses_ALL_window_limit_10_minReviews_3()]
replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::bestRatedItems → KILLED

119

1.1
Location : worstRatedItems
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:items_worst_default_params_calls_findBottomRatedItems_and_returns_shape()]
replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::worstRatedItems → KILLED

131

1.1
Location : commonsAverages
Killed by : edu.ucsb.cs156.dining.controllers.StatisticsControllerTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.dining.controllers.StatisticsControllerTests]/[method:commons_averages_default_window_ALL_returns_expected_shape()]
replaced return value with Collections.emptyList for edu/ucsb/cs156/dining/controllers/StatisticsController::commonsAverages → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0