@Test
  public void delete_characteristic() {
    DbSession batchSession = mock(DbSession.class);
    when(dbClient.openSession(true)).thenReturn(batchSession);

    when(ruleDao.findRulesByDebtSubCharacteristicId(batchSession, subCharacteristicDto.getId()))
        .thenReturn(
            newArrayList(
                new RuleDto()
                    .setSubCharacteristicId(subCharacteristicDto.getId())
                    .setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.toString())
                    .setRemediationCoefficient("2h")
                    .setRemediationOffset("5min")));
    when(dao.selectCharacteristicsByParentId(1, batchSession))
        .thenReturn(newArrayList(subCharacteristicDto));
    when(dao.selectById(1, batchSession)).thenReturn(characteristicDto);

    service.delete(1);

    verify(ruleDao).update(eq(batchSession), ruleCaptor.capture());

    verify(dao, times(2)).update(characteristicCaptor.capture(), eq(batchSession));
    CharacteristicDto subCharacteristicDto = characteristicCaptor.getAllValues().get(0);
    CharacteristicDto characteristicDto = characteristicCaptor.getAllValues().get(1);

    // Sub characteristic is disable
    assertThat(subCharacteristicDto.getId()).isEqualTo(2);
    assertThat(subCharacteristicDto.isEnabled()).isFalse();
    assertThat(subCharacteristicDto.getUpdatedAt()).isEqualTo(now);

    // Characteristic is disable
    assertThat(characteristicDto.getId()).isEqualTo(1);
    assertThat(characteristicDto.isEnabled()).isFalse();
    assertThat(characteristicDto.getUpdatedAt()).isEqualTo(now);
  }
  @VisibleForTesting
  List<CharacteristicDto> restoreCharacteristics(
      DebtModel targetModel, Date updateDate, DbSession session) {
    List<CharacteristicDto> sourceCharacteristics =
        dbClient.debtCharacteristicDao().selectEnabledCharacteristics(session);

    List<CharacteristicDto> result = newArrayList();

    // Create new characteristics
    for (DebtCharacteristic characteristic : targetModel.rootCharacteristics()) {
      CharacteristicDto rootCharacteristicDto =
          restoreCharacteristic(characteristic, null, sourceCharacteristics, updateDate, session);
      result.add(rootCharacteristicDto);
      for (DebtCharacteristic subCharacteristic :
          targetModel.subCharacteristics(characteristic.key())) {
        result.add(
            restoreCharacteristic(
                subCharacteristic,
                rootCharacteristicDto.getId(),
                sourceCharacteristics,
                updateDate,
                session));
      }
    }
    // Disable no more existing characteristics
    for (CharacteristicDto sourceCharacteristic : sourceCharacteristics) {
      if (targetModel.characteristicByKey(sourceCharacteristic.getKey()) == null) {
        debtModelOperations.delete(sourceCharacteristic, updateDate, session);
      }
    }
    return result;
  }
  private String backupFromLanguage(@Nullable String languageKey) {
    checkPermission();

    DbSession session = dbClient.openSession(false);
    try {
      DebtModel debtModel = new DebtModel();
      List<CharacteristicDto> characteristicDtos =
          dbClient.debtCharacteristicDao().selectEnabledCharacteristics(session);
      for (CharacteristicDto characteristicDto : characteristicDtos) {
        if (characteristicDto.getParentId() == null) {
          debtModel.addRootCharacteristic(toDebtCharacteristic(characteristicDto));
          for (CharacteristicDto sub :
              subCharacteristics(characteristicDto.getId(), characteristicDtos)) {
            debtModel.addSubCharacteristic(toDebtCharacteristic(sub), characteristicDto.getKey());
          }
        }
      }

      List<RuleDebt> rules = newArrayList();
      for (RuleDto rule : dbClient.ruleDao().selectEnabledAndNonManual(session)) {
        if (languageKey == null || languageKey.equals(rule.getLanguage())) {
          RuleDebt ruleDebt = toRuleDebt(rule, debtModel);
          if (ruleDebt != null) {
            rules.add(ruleDebt);
          }
        }
      }
      return debtModelXMLExporter.export(debtModel, rules);
    } finally {
      MyBatis.closeQuietly(session);
    }
  }
 private CharacteristicDto restoreCharacteristic(
     DebtCharacteristic targetCharacteristic,
     @Nullable Integer parentId,
     List<CharacteristicDto> sourceCharacteristics,
     Date updateDate,
     DbSession session) {
   CharacteristicDto sourceCharacteristic =
       characteristicByKey(targetCharacteristic.key(), sourceCharacteristics, false);
   if (sourceCharacteristic == null) {
     CharacteristicDto newCharacteristic =
         toDto(targetCharacteristic, parentId).setCreatedAt(updateDate);
     dbClient.debtCharacteristicDao().insert(newCharacteristic, session);
     return newCharacteristic;
   } else {
     // Update only if modifications
     if (ObjectUtils.notEqual(sourceCharacteristic.getName(), targetCharacteristic.name())
         || ObjectUtils.notEqual(sourceCharacteristic.getOrder(), targetCharacteristic.order())
         || ObjectUtils.notEqual(sourceCharacteristic.getParentId(), parentId)) {
       sourceCharacteristic.setName(targetCharacteristic.name());
       sourceCharacteristic.setOrder(targetCharacteristic.order());
       sourceCharacteristic.setParentId(parentId);
       sourceCharacteristic.setUpdatedAt(updateDate);
       dbClient.debtCharacteristicDao().update(sourceCharacteristic, session);
     }
     return sourceCharacteristic;
   }
 }
  @Test
  public void update_parent_when_restoring_characteristics() {
    when(dao.selectEnabledCharacteristics(session))
        .thenReturn(
            newArrayList(
                // Parent has changed
                new CharacteristicDto()
                    .setId(1)
                    .setKey("PORTABILITY")
                    .setName("Portability updated")
                    .setParentId(1)
                    .setOrder(1)
                    .setCreatedAt(oldDate)
                    .setUpdatedAt(oldDate),
                new CharacteristicDto()
                    .setId(2)
                    .setKey("COMPILER")
                    .setName("Compiler updated")
                    .setCreatedAt(oldDate)
                    .setUpdatedAt(oldDate)));

    debtModelBackup.restoreCharacteristics(
        new DebtModel()
            .addRootCharacteristic(
                new DefaultDebtCharacteristic()
                    .setKey("PORTABILITY")
                    .setName("Portability")
                    .setOrder(1))
            .addSubCharacteristic(
                new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"),
                "PORTABILITY"),
        now,
        session);

    verify(dao, times(2)).update(characteristicCaptor.capture(), eq(session));

    CharacteristicDto dto1 = characteristicCaptor.getAllValues().get(0);
    assertThat(dto1.getId()).isEqualTo(1);
    assertThat(dto1.getKey()).isEqualTo("PORTABILITY");
    assertThat(dto1.getParentId()).isNull();
    assertThat(dto1.getUpdatedAt()).isEqualTo(now);

    CharacteristicDto dto2 = characteristicCaptor.getAllValues().get(1);
    assertThat(dto2.getId()).isEqualTo(2);
    assertThat(dto2.getKey()).isEqualTo("COMPILER");
    assertThat(dto2.getParentId()).isEqualTo(1);
    assertThat(dto2.getUpdatedAt()).isEqualTo(now);
  }
  @Test
  public void delete_sub_characteristic() {
    DbSession batchSession = mock(DbSession.class);
    when(dbClient.openSession(true)).thenReturn(batchSession);

    when(ruleDao.findRulesByDebtSubCharacteristicId(batchSession, 2))
        .thenReturn(
            newArrayList(
                new RuleDto()
                    .setSubCharacteristicId(2)
                    .setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.toString())
                    .setRemediationCoefficient("2h")
                    .setRemediationOffset("5min")
                    .setDefaultSubCharacteristicId(10)
                    .setDefaultRemediationFunction(
                        DebtRemediationFunction.Type.LINEAR_OFFSET.toString())
                    .setDefaultRemediationCoefficient("4h")
                    .setDefaultRemediationOffset("15min")));
    when(dao.selectById(2, batchSession)).thenReturn(subCharacteristicDto);

    service.delete(2);

    verify(ruleDao).update(eq(batchSession), ruleCaptor.capture());

    RuleDto ruleDto = ruleCaptor.getValue();
    assertThat(ruleDto.getUpdatedAt()).isEqualTo(now);

    // Overridden debt data are disabled
    assertThat(ruleDto.getSubCharacteristicId()).isEqualTo(-1);
    assertThat(ruleDto.getRemediationFunction()).isNull();
    assertThat(ruleDto.getRemediationCoefficient()).isNull();
    assertThat(ruleDto.getRemediationOffset()).isNull();

    // Default debt data should not be touched
    assertThat(ruleDto.getDefaultSubCharacteristicId()).isEqualTo(10);
    assertThat(ruleDto.getDefaultRemediationFunction()).isEqualTo("LINEAR_OFFSET");
    assertThat(ruleDto.getDefaultRemediationCoefficient()).isEqualTo("4h");
    assertThat(ruleDto.getDefaultRemediationOffset()).isEqualTo("15min");

    verify(dao).update(characteristicCaptor.capture(), eq(batchSession));
    CharacteristicDto characteristicDto = characteristicCaptor.getValue();

    // Sub characteristic is disable
    assertThat(characteristicDto.getId()).isEqualTo(2);
    assertThat(characteristicDto.isEnabled()).isFalse();
    assertThat(characteristicDto.getUpdatedAt()).isEqualTo(now);
  }
  @Test
  public void search_by_has_debt_characteristic() throws InterruptedException {
    CharacteristicDto char1 =
        DebtTesting.newCharacteristicDto("c1").setEnabled(true).setName("char1");
    db.debtCharacteristicDao().insert(char1, dbSession);
    dbSession.commit();

    CharacteristicDto char11 =
        DebtTesting.newCharacteristicDto("c11")
            .setEnabled(true)
            .setName("char11")
            .setParentId(char1.getId());
    db.debtCharacteristicDao().insert(char11, dbSession);

    // Rule with default characteristic
    dao.insert(
        dbSession,
        RuleTesting.newDto(RuleKey.of("findbugs", "S001"))
            .setSubCharacteristicId(null)
            .setRemediationFunction(null)
            .setDefaultSubCharacteristicId(char11.getId())
            .setDefaultRemediationFunction("LINEAR")
            .setDefaultRemediationCoefficient("2h"));
    // Rule with overridden characteristic
    dao.insert(
        dbSession,
        RuleTesting.newDto(RuleKey.of("pmd", "S002"))
            .setSubCharacteristicId(char11.getId())
            .setRemediationFunction("LINEAR")
            .setRemediationCoefficient("2h")
            .setDefaultSubCharacteristicId(null)
            .setDefaultRemediationFunction(null));
    dbSession.commit();

    // 0. assert base case
    assertThat(index.search(new RuleQuery(), new QueryContext()).getTotal()).isEqualTo(2);
    assertThat(db.debtCharacteristicDao().selectCharacteristics()).hasSize(2);

    // 1. assert hasSubChar filter
    assertThat(
            index
                .search(new RuleQuery().setHasDebtCharacteristic(true), new QueryContext())
                .getTotal())
        .isEqualTo(2);
  }
  private void resetRules(
      List<RuleDto> ruleDtos,
      List<RulesDefinition.Rule> rules,
      List<CharacteristicDto> allCharacteristicDtos,
      Date updateDate,
      DbSession session) {
    for (RuleDto rule : ruleDtos) {
      // Restore default debt definitions

      RulesDefinition.Rule ruleDef;
      if (rule.getTemplateId() != null) {
        RuleDto templateRule = rule(rule.getTemplateId(), ruleDtos);
        ruleDef = ruleDef(templateRule.getRepositoryKey(), templateRule.getRuleKey(), rules);
      } else {
        ruleDef = ruleDef(rule.getRepositoryKey(), rule.getRuleKey(), rules);
      }

      if (ruleDef != null) {
        String subCharacteristicKey = ruleDef.debtSubCharacteristic();
        CharacteristicDto subCharacteristicDto =
            characteristicByKey(subCharacteristicKey, allCharacteristicDtos, false);
        DebtRemediationFunction remediationFunction = ruleDef.debtRemediationFunction();
        boolean hasDebtDefinition = subCharacteristicDto != null && remediationFunction != null;

        rule.setDefaultSubCharacteristicId(hasDebtDefinition ? subCharacteristicDto.getId() : null);
        rule.setDefaultRemediationFunction(
            hasDebtDefinition ? remediationFunction.type().name() : null);
        rule.setDefaultRemediationCoefficient(
            hasDebtDefinition ? remediationFunction.coefficient() : null);
        rule.setDefaultRemediationOffset(hasDebtDefinition ? remediationFunction.offset() : null);
      }

      // Reset overridden debt definitions
      rule.setSubCharacteristicId(null);
      rule.setRemediationFunction(null);
      rule.setRemediationCoefficient(null);
      rule.setRemediationOffset(null);
      rule.setUpdatedAt(updateDate);
      dbClient.ruleDao().update(session, rule);
    }
  }
 private static DebtCharacteristic toDebtCharacteristic(CharacteristicDto characteristic) {
   return new DefaultDebtCharacteristic()
       .setId(characteristic.getId())
       .setKey(characteristic.getKey())
       .setName(characteristic.getName())
       .setOrder(characteristic.getOrder())
       .setParentId(characteristic.getParentId())
       .setCreatedAt(characteristic.getCreatedAt())
       .setUpdatedAt(characteristic.getUpdatedAt());
 }
  @Test
  public void search_by_characteristics_with_default_and_overridden_char()
      throws InterruptedException {
    CharacteristicDto char1 = DebtTesting.newCharacteristicDto("RELIABILITY");
    db.debtCharacteristicDao().insert(char1, dbSession);

    CharacteristicDto char11 =
        DebtTesting.newCharacteristicDto("SOFT_RELIABILITY").setParentId(char1.getId());
    db.debtCharacteristicDao().insert(char11, dbSession);
    dbSession.commit();

    CharacteristicDto char2 = DebtTesting.newCharacteristicDto("TESTABILITY");
    db.debtCharacteristicDao().insert(char2, dbSession);

    CharacteristicDto char21 =
        DebtTesting.newCharacteristicDto("UNIT_TESTABILITY").setParentId(char2.getId());
    db.debtCharacteristicDao().insert(char21, dbSession);
    dbSession.commit();

    // Rule with only default sub characteristic -> should be find by char11 and char1
    dao.insert(
        dbSession,
        RuleTesting.newDto(RuleKey.of("java", "S001"))
            .setSubCharacteristicId(char11.getId())
            .setDefaultSubCharacteristicId(null));

    // Rule with only sub characteristic -> should be find by char11 and char1
    dao.insert(
        dbSession,
        RuleTesting.newDto(RuleKey.of("java", "S002"))
            .setSubCharacteristicId(null)
            .setDefaultSubCharacteristicId(char11.getId()));

    // Rule with both default sub characteristic and overridden sub characteristic -> should only be
    // find by char21 and char2
    dao.insert(
            dbSession,
            RuleTesting.newDto(RuleKey.of("java", "S003")).setSubCharacteristicId(char21.getId()))
        .setDefaultSubCharacteristicId(char11.getId());

    // Rule with both default sub characteristic and overridden sub characteristic and with same
    // values -> should be find by char11 and char1
    dao.insert(
            dbSession,
            RuleTesting.newDto(RuleKey.of("java", "S004")).setSubCharacteristicId(char11.getId()))
        .setDefaultSubCharacteristicId(char11.getId());

    dbSession.commit();
    dbSession.clearCache();

    RuleQuery query;
    Result<Rule> results;

    // 0. we have 4 rules in index
    results = index.search(new RuleQuery(), new QueryContext());
    assertThat(results.getHits()).hasSize(4);

    // filter by subChar
    query = new RuleQuery().setDebtCharacteristics(ImmutableSet.of(char11.getKey()));
    assertThat(ruleKeys(index.search(query, new QueryContext()).getHits()))
        .containsOnly("S001", "S002", "S004");

    query = new RuleQuery().setDebtCharacteristics(ImmutableSet.of(char21.getKey()));
    assertThat(ruleKeys(index.search(query, new QueryContext()).getHits())).containsOnly("S003");

    // filter by Char
    query = new RuleQuery().setDebtCharacteristics(ImmutableSet.of(char1.getKey()));
    assertThat(ruleKeys(index.search(query, new QueryContext()).getHits()))
        .containsOnly("S001", "S002", "S004");

    query = new RuleQuery().setDebtCharacteristics(ImmutableSet.of(char2.getKey()));
    assertThat(ruleKeys(index.search(query, new QueryContext()).getHits())).containsOnly("S003");

    // filter by Char and SubChar
    query =
        new RuleQuery()
            .setDebtCharacteristics(
                ImmutableSet.of(char11.getKey(), char1.getKey(), char2.getKey(), char21.getKey()));
    assertThat(ruleKeys(index.search(query, new QueryContext()).getHits()))
        .containsOnly("S001", "S002", "S003", "S004");
  }
  @Test
  public void search_by_characteristics() throws InterruptedException {
    CharacteristicDto char1 = DebtTesting.newCharacteristicDto("RELIABILITY");
    db.debtCharacteristicDao().insert(char1, dbSession);

    CharacteristicDto char11 =
        DebtTesting.newCharacteristicDto("SOFT_RELIABILITY").setParentId(char1.getId());
    db.debtCharacteristicDao().insert(char11, dbSession);
    dbSession.commit();

    dao.insert(
        dbSession,
        RuleTesting.newDto(RuleKey.of("java", "S001")).setSubCharacteristicId(char11.getId()));

    dao.insert(dbSession, RuleTesting.newDto(RuleKey.of("javascript", "S002")));

    dbSession.commit();
    dbSession.clearCache();

    RuleQuery query;
    Result<Rule> results;

    // 0. we have 2 rules in index
    results = index.search(new RuleQuery(), new QueryContext());
    assertThat(results.getHits()).hasSize(2);

    // filter by non-subChar
    query = new RuleQuery().setDebtCharacteristics(ImmutableSet.of("toto"));
    assertThat(index.search(query, new QueryContext()).getHits()).isEmpty();

    // filter by subChar
    query = new RuleQuery().setDebtCharacteristics(ImmutableSet.of(char11.getKey()));
    assertThat(index.search(query, new QueryContext()).getHits()).hasSize(1);

    // filter by Char
    query = new RuleQuery().setDebtCharacteristics(ImmutableSet.of(char1.getKey()));
    assertThat(index.search(query, new QueryContext()).getHits()).hasSize(1);

    // filter by Char and SubChar
    query =
        new RuleQuery().setDebtCharacteristics(ImmutableSet.of(char11.getKey(), char1.getKey()));
    assertThat(index.search(query, new QueryContext()).getHits()).hasSize(1);

    // match by Char
    query = new RuleQuery().setQueryText(char1.getKey());
    assertThat(index.search(query, new QueryContext()).getHits()).hasSize(1);

    // match by SubChar
    query = new RuleQuery().setQueryText(char11.getKey());
    assertThat(index.search(query, new QueryContext()).getHits()).hasSize(1);

    // match by SubChar & Char
    query = new RuleQuery().setQueryText(char11.getKey() + " " + char1.getKey());
    assertThat(index.search(query, new QueryContext()).getHits()).hasSize(1);
  }
  @Test
  public void create_characteristics_when_restoring_characteristics() {
    when(dao.selectEnabledCharacteristics(session))
        .thenReturn(Collections.<CharacteristicDto>emptyList());

    debtModelBackup.restoreCharacteristics(
        new DebtModel()
            .addRootCharacteristic(
                new DefaultDebtCharacteristic()
                    .setKey("PORTABILITY")
                    .setName("Portability")
                    .setOrder(1))
            .addSubCharacteristic(
                new DefaultDebtCharacteristic().setKey("COMPILER").setName("Compiler"),
                "PORTABILITY"),
        now,
        session);

    verify(dao, times(2)).insert(characteristicCaptor.capture(), eq(session));

    CharacteristicDto dto1 = characteristicCaptor.getAllValues().get(0);
    assertThat(dto1.getId()).isEqualTo(10);
    assertThat(dto1.getKey()).isEqualTo("PORTABILITY");
    assertThat(dto1.getName()).isEqualTo("Portability");
    assertThat(dto1.getParentId()).isNull();
    assertThat(dto1.getOrder()).isEqualTo(1);
    assertThat(dto1.getCreatedAt()).isEqualTo(now);
    assertThat(dto1.getUpdatedAt()).isNull();

    CharacteristicDto dto2 = characteristicCaptor.getAllValues().get(1);
    assertThat(dto2.getId()).isEqualTo(11);
    assertThat(dto2.getKey()).isEqualTo("COMPILER");
    assertThat(dto2.getName()).isEqualTo("Compiler");
    assertThat(dto2.getParentId()).isEqualTo(10);
    assertThat(dto2.getOrder()).isNull();
    assertThat(dto2.getCreatedAt()).isEqualTo(now);
    assertThat(dto2.getUpdatedAt()).isNull();
  }