/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.db.property;
import java.util.Map;
import java.util.Objects;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.assertj.core.api.AbstractAssert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class InternalPropertiesDaoTest {
private static final String EMPTY_STRING = "";
private static final String A_KEY = "a_key";
private static final String VALUE_1 = "one";
private static final String VALUE_2 = "two";
private static final long DATE_1 = 1_500_000_000_000L;
private static final long DATE_2 = 1_600_000_000_000L;
private static final String VALUE_SMALL = "some small value";
private static final String VALUE_SIZE_4000 = String.format("%1$4000.4000s", "*");
private static final String VALUE_SIZE_4001 = VALUE_SIZE_4000 + "P";
private System2 system2 = mock(System2.class);
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public DbTester dbTester = DbTester.create(system2);
private DbSession dbSession = dbTester.getSession();
private InternalPropertiesDao underTest = new InternalPropertiesDao(system2);
@Test
public void save_throws_IAE_if_key_is_null() {
expectKeyNullOrEmptyIAE();
underTest.save(dbSession, null, VALUE_SMALL);
}
@Test
public void save_throws_IAE_if_key_is_empty() {
expectKeyNullOrEmptyIAE();
underTest.save(dbSession, EMPTY_STRING, VALUE_SMALL);
}
@Test
public void save_throws_IAE_if_value_is_null() {
expectValueNullOrEmptyIAE();
underTest.save(dbSession, A_KEY, null);
}
@Test
public void save_throws_IAE_if_value_is_empty() {
expectValueNullOrEmptyIAE();
underTest.save(dbSession, A_KEY, EMPTY_STRING);
}
@Test
public void save_persists_value_in_varchar_if_less_than_4000() {
when(system2.now()).thenReturn(DATE_2);
underTest.save(dbSession, A_KEY, VALUE_SMALL);
assertThatInternalProperty(A_KEY)
.hasTextValue(VALUE_SMALL)
.hasCreatedAt(DATE_2);
}
@Test
public void save_persists_value_in_varchar_if_4000() {
when(system2.now()).thenReturn(DATE_1);
underTest.save(dbSession, A_KEY, VALUE_SIZE_4000);
assertThatInternalProperty(A_KEY)
.hasTextValue(VALUE_SIZE_4000)
.hasCreatedAt(DATE_1);
}
@Test
public void save_persists_value_in_varchar_if_more_than_4000() {
when(system2.now()).thenReturn(DATE_2);
underTest.save(dbSession, A_KEY, VALUE_SIZE_4001);
assertThatInternalProperty(A_KEY)
.hasClobValue(VALUE_SIZE_4001)
.hasCreatedAt(DATE_2);
}
@Test
public void save_persists_new_value_in_varchar_if_4000_when_old_one_was_in_varchar() {
when(system2.now()).thenReturn(DATE_1, DATE_2);
underTest.save(dbSession, A_KEY, VALUE_SMALL);
assertThatInternalProperty(A_KEY)
.hasTextValue(VALUE_SMALL)
.hasCreatedAt(DATE_1);
underTest.save(dbSession, A_KEY, VALUE_SIZE_4000);
assertThatInternalProperty(A_KEY)
.hasTextValue(VALUE_SIZE_4000)
.hasCreatedAt(DATE_2);
}
@Test
public void save_persists_new_value_in_clob_if_more_than_4000_when_old_one_was_in_varchar() {
when(system2.now()).thenReturn(DATE_1, DATE_2);
underTest.save(dbSession, A_KEY, VALUE_SMALL);
assertThatInternalProperty(A_KEY)
.hasTextValue(VALUE_SMALL)
.hasCreatedAt(DATE_1);
underTest.save(dbSession, A_KEY, VALUE_SIZE_4001);
assertThatInternalProperty(A_KEY)
.hasClobValue(VALUE_SIZE_4001)
.hasCreatedAt(DATE_2);
}
@Test
public void save_persists_new_value_in_varchar_if_less_than_4000_when_old_one_was_in_clob() {
when(system2.now()).thenReturn(DATE_1, DATE_2);
underTest.save(dbSession, A_KEY, VALUE_SIZE_4001);
assertThatInternalProperty(A_KEY)
.hasClobValue(VALUE_SIZE_4001)
.hasCreatedAt(DATE_1);
underTest.save(dbSession, A_KEY, VALUE_SMALL);
assertThatInternalProperty(A_KEY)
.hasTextValue(VALUE_SMALL)
.hasCreatedAt(DATE_2);
}
@Test
public void save_persists_new_value_in_clob_if_more_than_4000_when_old_one_was_in_clob() {
when(system2.now()).thenReturn(DATE_1, DATE_2);
String oldValue = VALUE_SIZE_4001 + "blabla";
underTest.save(dbSession, A_KEY, oldValue);
assertThatInternalProperty(A_KEY)
.hasClobValue(oldValue)
.hasCreatedAt(DATE_1);
underTest.save(dbSession, A_KEY, VALUE_SIZE_4001);
assertThatInternalProperty(A_KEY)
.hasClobValue(VALUE_SIZE_4001)
.hasCreatedAt(DATE_2);
}
@Test
public void saveAsEmpty_throws_IAE_if_key_is_null() {
expectKeyNullOrEmptyIAE();
underTest.saveAsEmpty(dbSession, null);
}
@Test
public void saveAsEmpty_throws_IAE_if_key_is_empty() {
expectKeyNullOrEmptyIAE();
underTest.saveAsEmpty(dbSession, EMPTY_STRING);
}
@Test
public void saveAsEmpty_persist_property_without_textvalue_nor_clob_value() {
when(system2.now()).thenReturn(DATE_2);
underTest.saveAsEmpty(dbSession, A_KEY);
assertThatInternalProperty(A_KEY)
.isEmpty()
.hasCreatedAt(DATE_2);
}
@Test
public void saveAsEmpty_persist_property_without_textvalue_nor_clob_value_when_old_value_was_in_varchar() {
when(system2.now()).thenReturn(DATE_1, DATE_2);
underTest.save(dbSession, A_KEY, VALUE_SMALL);
assertThatInternalProperty(A_KEY)
.hasTextValue(VALUE_SMALL)
.hasCreatedAt(DATE_1);
underTest.saveAsEmpty(dbSession, A_KEY);
assertThatInternalProperty(A_KEY)
.isEmpty()
.hasCreatedAt(DATE_2);
}
@Test
public void saveAsEmpty_persist_property_without_textvalue_nor_clob_value_when_old_value_was_in_clob() {
when(system2.now()).thenReturn(DATE_2, DATE_1);
underTest.save(dbSession, A_KEY, VALUE_SIZE_4001);
assertThatInternalProperty(A_KEY)
.hasClobValue(VALUE_SIZE_4001)
.hasCreatedAt(DATE_2);
underTest.saveAsEmpty(dbSession, A_KEY);
assertThatInternalProperty(A_KEY)
.isEmpty()
.hasCreatedAt(DATE_1);
}
@Test
public void selectByKey_throws_IAE_when_key_is_null() {
expectKeyNullOrEmptyIAE();
underTest.selectByKey(dbSession, null);
}
@Test
public void selectByKey_throws_IAE_when_key_is_empty() {
expectKeyNullOrEmptyIAE();
underTest.selectByKey(dbSession, EMPTY_STRING);
}
@Test
public void selectByKey_returns_empty_optional_when_property_does_not_exist_in_DB() {
assertThat(underTest.selectByKey(dbSession, A_KEY)).isEmpty();
}
@Test
public void selectByKey_returns_empty_string_when_property_is_empty_in_DB() {
underTest.saveAsEmpty(dbSession, A_KEY);
assertThat(underTest.selectByKey(dbSession, A_KEY)).contains(EMPTY_STRING);
}
@Test
public void selectByKey_returns_value_when_property_has_value_stored_in_varchar() {
underTest.save(dbSession, A_KEY, VALUE_SMALL);
assertThat(underTest.selectByKey(dbSession, A_KEY)).contains(VALUE_SMALL);
}
@Test
public void selectByKey_returns_value_when_property_has_value_stored_in_clob() {
underTest.save(dbSession, A_KEY, VALUE_SIZE_4001);
assertThat(underTest.selectByKey(dbSession, A_KEY)).contains(VALUE_SIZE_4001);
}
private void expectKeyNullOrEmptyIAE() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("key can't be null nor empty");
}
private void expectValueNullOrEmptyIAE() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("value can't be null nor empty");
}
private InternalPropertyAssert assertThatInternalProperty(String key) {
return new InternalPropertyAssert(dbTester, dbSession, key);
}
private static class InternalPropertyAssert extends AbstractAssert<InternalPropertyAssert, InternalProperty> {
private InternalPropertyAssert(DbTester dbTester, DbSession dbSession, String internalPropertyKey) {
super(asInternalProperty(dbTester, dbSession, internalPropertyKey), InternalPropertyAssert.class);
}
private static InternalProperty asInternalProperty(DbTester dbTester, DbSession dbSession, String internalPropertyKey) {
Map<String, Object> row = dbTester.selectFirst(
dbSession,
"select" +
" is_empty as \"isEmpty\", text_value as \"textValue\", clob_value as \"clobValue\", created_at as \"createdAt\"" +
" from internal_properties" +
" where kee='" + internalPropertyKey + "'");
return new InternalProperty(
isEmpty(row),
(String) row.get("textValue"),
(String) row.get("clobValue"),
(Long) row.get("createdAt"));
}
private static Boolean isEmpty(Map<String, Object> row) {
Object flag = row.get("isEmpty");
if (flag instanceof Boolean) {
return (Boolean) flag;
}
if (flag instanceof Long) {
Long longBoolean = (Long) flag;
return longBoolean.equals(1L);
}
throw new IllegalArgumentException("Unsupported object type returned for column \"isEmpty\": " + flag.getClass());
}
public void doesNotExist() {
isNull();
}
public InternalPropertyAssert isEmpty() {
isNotNull();
if (!Objects.equals(actual.isEmpty(), TRUE)) {
failWithMessage("Expected Internal property to have column IS_EMPTY to be <%s> but was <%s>", true, actual.isEmpty());
}
if (actual.getTextValue() != null) {
failWithMessage("Expected Internal property to have column TEXT_VALUE to be null but was <%s>", actual.getTextValue());
}
if (actual.getClobValue() != null) {
failWithMessage("Expected Internal property to have column CLOB_VALUE to be null but was <%s>", actual.getClobValue());
}
return this;
}
public InternalPropertyAssert hasTextValue(String expected) {
isNotNull();
if (!Objects.equals(actual.getTextValue(), expected)) {
failWithMessage("Expected Internal property to have column TEXT_VALUE to be <%s> but was <%s>", true, actual.getTextValue());
}
if (actual.getClobValue() != null) {
failWithMessage("Expected Internal property to have column CLOB_VALUE to be null but was <%s>", actual.getClobValue());
}
if (!Objects.equals(actual.isEmpty(), FALSE)) {
failWithMessage("Expected Internal property to have column IS_EMPTY to be <%s> but was <%s>", false, actual.isEmpty());
}
return this;
}
public InternalPropertyAssert hasClobValue(String expected) {
isNotNull();
if (!Objects.equals(actual.getClobValue(), expected)) {
failWithMessage("Expected Internal property to have column CLOB_VALUE to be <%s> but was <%s>", true, actual.getClobValue());
}
if (actual.getTextValue() != null) {
failWithMessage("Expected Internal property to have column TEXT_VALUE to be null but was <%s>", actual.getTextValue());
}
if (!Objects.equals(actual.isEmpty(), FALSE)) {
failWithMessage("Expected Internal property to have column IS_EMPTY to be <%s> but was <%s>", false, actual.isEmpty());
}
return this;
}
public InternalPropertyAssert hasCreatedAt(long expected) {
isNotNull();
if (!Objects.equals(actual.getCreatedAt(), expected)) {
failWithMessage("Expected Internal property to have column CREATED_AT to be <%s> but was <%s>", expected, actual.getCreatedAt());
}
return this;
}
}
private static final class InternalProperty {
private final Boolean empty;
private final String textValue;
private final String clobValue;
private final Long createdAt;
public InternalProperty(@Nullable Boolean empty, @Nullable String textValue, @Nullable String clobValue, @Nullable Long createdAt) {
this.empty = empty;
this.textValue = textValue;
this.clobValue = clobValue;
this.createdAt = createdAt;
}
@CheckForNull
public Boolean isEmpty() {
return empty;
}
@CheckForNull
public String getTextValue() {
return textValue;
}
@CheckForNull
public String getClobValue() {
return clobValue;
}
@CheckForNull
public Long getCreatedAt() {
return createdAt;
}
}
}