LCOV - code coverage report
Current view: top level - server/util - AttentionSetUtil.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 35 35 100.0 %
Date: 2022-11-19 15:00:39 Functions: 9 9 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2020 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.util;
      16             : 
      17             : import com.google.common.base.Strings;
      18             : import com.google.common.collect.ImmutableSet;
      19             : import com.google.common.collect.Iterables;
      20             : import com.google.gerrit.entities.Account;
      21             : import com.google.gerrit.entities.AttentionSetUpdate;
      22             : import com.google.gerrit.entities.AttentionSetUpdate.Operation;
      23             : import com.google.gerrit.extensions.api.changes.AttentionSetInput;
      24             : import com.google.gerrit.extensions.common.AccountInfo;
      25             : import com.google.gerrit.extensions.common.AttentionSetInfo;
      26             : import com.google.gerrit.extensions.restapi.BadRequestException;
      27             : import com.google.gerrit.server.account.AccountLoader;
      28             : import com.google.gerrit.server.account.AccountResolver;
      29             : import com.google.gerrit.server.notedb.ChangeNotes;
      30             : import java.io.IOException;
      31             : import java.util.Collection;
      32             : import org.eclipse.jgit.errors.ConfigInvalidException;
      33             : 
      34             : /** Common helpers for dealing with attention set data structures. */
      35             : public class AttentionSetUtil {
      36             : 
      37             :   /** Returns only updates where the user was added. */
      38             :   public static ImmutableSet<AttentionSetUpdate> additionsOnly(
      39             :       Collection<AttentionSetUpdate> updates) {
      40         103 :     return updates.stream()
      41         103 :         .filter(u -> u.operation() == Operation.ADD)
      42         103 :         .collect(ImmutableSet.toImmutableSet());
      43             :   }
      44             : 
      45             :   /** Returns only updates where the user was removed. */
      46             :   public static ImmutableSet<AttentionSetUpdate> removalsOnly(
      47             :       Collection<AttentionSetUpdate> updates) {
      48          49 :     return updates.stream()
      49          49 :         .filter(u -> u.operation() == Operation.REMOVE)
      50          49 :         .collect(ImmutableSet.toImmutableSet());
      51             :   }
      52             : 
      53             :   /**
      54             :    * Validates the input for AttentionSetInput. This must be called for all inputs that relate to
      55             :    * adding or removing attention set entries, except for {@link
      56             :    * com.google.gerrit.server.restapi.change.RemoveFromAttentionSet}.
      57             :    */
      58             :   public static void validateInput(AttentionSetInput input) throws BadRequestException {
      59          13 :     input.user = Strings.nullToEmpty(input.user).trim();
      60          13 :     if (input.user.isEmpty()) {
      61           2 :       throw new BadRequestException("missing field: user");
      62             :     }
      63          12 :     input.reason = Strings.nullToEmpty(input.reason).trim();
      64          12 :     if (input.reason.isEmpty()) {
      65           1 :       throw new BadRequestException("missing field: reason");
      66             :     }
      67          12 :   }
      68             : 
      69             :   /**
      70             :    * Returns the {@code Account.Id} of {@code user} if the user is active on the change, and exists.
      71             :    * If the user doesn't exist or is not active on the change, the same exception is thrown to
      72             :    * disallow probing for account existence based on exception type.
      73             :    */
      74             :   public static Account.Id resolveAccount(
      75             :       AccountResolver accountResolver, ChangeNotes changeNotes, String user)
      76             :       throws ConfigInvalidException, IOException, BadRequestException {
      77             :     // We will throw this exception if the account doesn't exist, or if the account is not active.
      78             :     // This is purposely the same exception so that users can't probe for account existence based on
      79             :     // the thrown exception.
      80          14 :     BadRequestException possibleExceptionForNotFoundOrInactiveAccount =
      81             :         new BadRequestException(
      82          14 :             String.format(
      83             :                 "%s doesn't exist or is not active on the change as an owner, uploader, "
      84             :                     + "reviewer, or cc so they can't be added to the attention set",
      85             :                 user));
      86             :     Account.Id attentionUserId;
      87             :     try {
      88          14 :       attentionUserId = accountResolver.resolveIgnoreVisibility(user).asUnique().account().id();
      89           1 :     } catch (AccountResolver.UnresolvableAccountException ex) {
      90           1 :       possibleExceptionForNotFoundOrInactiveAccount.initCause(ex);
      91           1 :       throw possibleExceptionForNotFoundOrInactiveAccount;
      92          14 :     }
      93          14 :     if (!isActiveOnTheChange(changeNotes, attentionUserId)) {
      94           2 :       throw possibleExceptionForNotFoundOrInactiveAccount;
      95             :     }
      96          13 :     return attentionUserId;
      97             :   }
      98             : 
      99             :   /**
     100             :    * Returns whether {@code attentionUserId} is active on a change. Activity is defined as being a
     101             :    * part of the reviewers, an uploader, or an owner of a change.
     102             :    */
     103             :   private static boolean isActiveOnTheChange(ChangeNotes changeNotes, Account.Id attentionUserId) {
     104          14 :     return changeNotes.getChange().getOwner().equals(attentionUserId)
     105           8 :         || changeNotes.getCurrentPatchSet().uploader().equals(attentionUserId)
     106          14 :         || changeNotes.getReviewers().all().stream().anyMatch(id -> id.equals(attentionUserId));
     107             :   }
     108             : 
     109             :   /**
     110             :    * Returns {@link AttentionSetInfo} from {@link AttentionSetUpdate} with {@link AccountInfo}
     111             :    * fields filled by {@code accountLoader}.
     112             :    */
     113             :   public static AttentionSetInfo createAttentionSetInfo(
     114             :       AttentionSetUpdate attentionSetUpdate, AccountLoader accountLoader) {
     115             :     // Only one account is expected in attention set reason. If there are multiple, do not return
     116             :     // anything instead of failing the request.
     117          49 :     ImmutableSet<Account.Id> accountsInTemplate =
     118          49 :         AccountTemplateUtil.parseTemplates(attentionSetUpdate.reason());
     119             :     AccountInfo reasonAccount =
     120          49 :         accountsInTemplate.size() == 1
     121           1 :             ? accountLoader.get(Iterables.getOnlyElement(accountsInTemplate))
     122          49 :             : null;
     123          49 :     return new AttentionSetInfo(
     124          49 :         accountLoader.get(attentionSetUpdate.account()),
     125          49 :         attentionSetUpdate.timestamp(),
     126          49 :         attentionSetUpdate.reason(),
     127             :         reasonAccount);
     128             :   }
     129             : 
     130             :   private AttentionSetUtil() {}
     131             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750