From 4f50219e05dd5e0db1dd02bd78c8bb8014cad997 Mon Sep 17 00:00:00 2001 From: mazenmelouk Date: Mon, 5 Feb 2024 19:48:47 +0100 Subject: [PATCH] Add ExchangeRateRecord entity The ExchangeRateRecord contains reference to the record with conversion rates as a mapping table. The ExchangeRateRepositoryIT runs a test to validate finding the latest conversion rate is correct. --- .../personal/data/ExchangeRateRecord.java | 77 +++++++++++++++++++ .../personal/data/ExchangeRateRepository.java | 12 +++ .../data/ExchangeRateRepositoryIT.java | 45 +++++++++++ 3 files changed, 134 insertions(+) create mode 100644 src/main/java/com/melouk/personal/data/ExchangeRateRecord.java create mode 100644 src/main/java/com/melouk/personal/data/ExchangeRateRepository.java create mode 100644 src/test/java/com/melouk/personal/data/ExchangeRateRepositoryIT.java diff --git a/src/main/java/com/melouk/personal/data/ExchangeRateRecord.java b/src/main/java/com/melouk/personal/data/ExchangeRateRecord.java new file mode 100644 index 0000000..69071e5 --- /dev/null +++ b/src/main/java/com/melouk/personal/data/ExchangeRateRecord.java @@ -0,0 +1,77 @@ +package com.melouk.personal.data; + + + +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.MapKeyColumn; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; + +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.Objects; + +@Entity +@Table(name = "exchange_rate_record") +public final class ExchangeRateRecord { + @Id + @GeneratedValue + @Column(name = "id") + private long id; + + private final ZonedDateTime timeLastUpdateUnix; + @OrderColumn(name = "source_currency") + private final String sourceCurrency; + @ElementCollection + @CollectionTable(name = "exchange_rate_mapping", + joinColumns = {@JoinColumn(name = "exchange_rate_record_id", referencedColumnName = "id")}) + @MapKeyColumn(name = "target_currency") + @Column(name = "rate") + private final Map conversionRates; + + public ExchangeRateRecord(ZonedDateTime timeLastUpdateUnix, + String sourceCurrency, + Map conversionRates) { + this.timeLastUpdateUnix = ZonedDateTime.from(timeLastUpdateUnix); + this.sourceCurrency = sourceCurrency; + this.conversionRates = Map.copyOf(conversionRates); + } + + public long getId() { + return id; + } + + public ZonedDateTime getTimeLastUpdateUnix() { + return timeLastUpdateUnix; + } + + public String getSourceCurrency() { + return sourceCurrency; + } + + public Map getConversionRates() { + return conversionRates; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ExchangeRateRecord that = (ExchangeRateRecord) o; + return id == that.id && + Objects.equals(timeLastUpdateUnix, that.timeLastUpdateUnix) && + Objects.equals(sourceCurrency, that.sourceCurrency) && + Objects.equals(conversionRates, that.conversionRates); + } + + @Override + public int hashCode() { + return Objects.hash(id, timeLastUpdateUnix, sourceCurrency, conversionRates); + } +} diff --git a/src/main/java/com/melouk/personal/data/ExchangeRateRepository.java b/src/main/java/com/melouk/personal/data/ExchangeRateRepository.java new file mode 100644 index 0000000..2a3f22d --- /dev/null +++ b/src/main/java/com/melouk/personal/data/ExchangeRateRepository.java @@ -0,0 +1,12 @@ +package com.melouk.personal.data; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ExchangeRateRepository extends JpaRepository { + + Optional findTopBySourceCurrencyOrderByTimeLastUpdateUnixDesc(String sourceCurrency); +} diff --git a/src/test/java/com/melouk/personal/data/ExchangeRateRepositoryIT.java b/src/test/java/com/melouk/personal/data/ExchangeRateRepositoryIT.java new file mode 100644 index 0000000..8db7b8c --- /dev/null +++ b/src/test/java/com/melouk/personal/data/ExchangeRateRepositoryIT.java @@ -0,0 +1,45 @@ +package com.melouk.personal.data; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(SpringExtension.class) +@DataJpaTest +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +class ExchangeRateRepositoryIT { + + private static final Map USD_TO_EUR = Map.of("EUR", 1.0); + private static final String USD = "USD"; + @Autowired + private ExchangeRateRepository rateRepository; + + @Test + void testFindLatestForSameSourceCurrency() { + ExchangeRateRecord yesterdayRate = + new ExchangeRateRecord(ZonedDateTime.now().minusDays(1), USD, USD_TO_EUR); + + rateRepository.save(yesterdayRate); + ExchangeRateRecord todayRate = + new ExchangeRateRecord(ZonedDateTime.now(), USD, USD_TO_EUR); + ExchangeRateRecord expected = rateRepository.save(todayRate); + Optional test = rateRepository.findTopBySourceCurrencyOrderByTimeLastUpdateUnixDesc(USD); + assertThat(test).isPresent().hasValue(expected); + } + + @Test + void testReturnsEmptyForNonPersistedRates() { + Optional test = rateRepository.findTopBySourceCurrencyOrderByTimeLastUpdateUnixDesc("EUR"); + + assertThat(test).isEmpty(); + } +} \ No newline at end of file