source, targetDate 그룹에서 최신만 선택
- 조회 시, 과거 데이터 무시 - 저장 자체를 최신 데이터만 해도 되긴하는데, 디버깅 용도 혹은 다른 방법으로 쓰일 걸 염두에 두고, 그냥 냅둔다.
This commit is contained in:
@@ -90,12 +90,41 @@ func (*Flow) mergeWeathers(dateKey string, location *time.Location, weathers []*
|
||||
date, _ := time.Parse("2006-01-02", dateKey)
|
||||
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, location)
|
||||
|
||||
high := float64(weathers[0].Temperature().Value)
|
||||
low := float64(weathers[0].Temperature().Value)
|
||||
worstCondition := weathers[0].Condition()
|
||||
// Source와 TargetDate가 동일한 경우, ForecastDate가 최신 항목만 선택
|
||||
filteredWeathers := make(map[string]*domain.Weather)
|
||||
|
||||
for _, w := range weathers {
|
||||
temp := float64(w.Temperature().Value)
|
||||
for _, weather := range weathers {
|
||||
key := string(weather.Source()) + weather.TargetDate().Format("2006-01-02")
|
||||
|
||||
existing, ok := filteredWeathers[key]
|
||||
if !ok {
|
||||
filteredWeathers[key] = weather
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if weather.ForecastDate().Before(existing.ForecastDate()) {
|
||||
continue
|
||||
}
|
||||
|
||||
filteredWeathers[key] = weather
|
||||
}
|
||||
|
||||
var filteredSlice []*domain.Weather
|
||||
for _, weather := range filteredWeathers {
|
||||
filteredSlice = append(filteredSlice, weather)
|
||||
}
|
||||
|
||||
if len(filteredSlice) == 0 {
|
||||
panic("no weather found")
|
||||
}
|
||||
|
||||
high := float64(filteredSlice[0].Temperature().Value)
|
||||
low := float64(filteredSlice[0].Temperature().Value)
|
||||
worstCondition := filteredSlice[0].Condition()
|
||||
|
||||
for _, weather := range filteredSlice {
|
||||
temp := float64(weather.Temperature().Value)
|
||||
if temp > high {
|
||||
high = temp
|
||||
}
|
||||
@@ -104,19 +133,17 @@ func (*Flow) mergeWeathers(dateKey string, location *time.Location, weathers []*
|
||||
low = temp
|
||||
}
|
||||
|
||||
if w.Condition().IsWorseThan(worstCondition) {
|
||||
worstCondition = w.Condition()
|
||||
if weather.Condition().IsWorseThan(worstCondition) {
|
||||
worstCondition = weather.Condition()
|
||||
}
|
||||
}
|
||||
|
||||
newVar := &DailyWeather{
|
||||
return &DailyWeather{
|
||||
Date: date,
|
||||
High: high,
|
||||
Low: low,
|
||||
Condition: string(worstCondition),
|
||||
}
|
||||
|
||||
return newVar
|
||||
}
|
||||
|
||||
func (*Flow) groupByDate(weathers []*domain.Weather, location *time.Location) map[string][]*domain.Weather {
|
||||
|
||||
111
internal/app/flow/flow_test.go
Normal file
111
internal/app/flow/flow_test.go
Normal file
@@ -0,0 +1,111 @@
|
||||
//nolint:dupl
|
||||
package flow_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/neatflowcv/seven-skies/internal/app/flow"
|
||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
||||
"github.com/neatflowcv/seven-skies/internal/pkg/repository/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestFlow_ListDailyWeathers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mocks.NewMockRepository(ctrl)
|
||||
now := time.Now()
|
||||
mock.EXPECT().ListWeathers(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*domain.Weather{
|
||||
domain.NewWeather(
|
||||
"1",
|
||||
domain.WeatherSourceOpenWeather,
|
||||
now,
|
||||
now.Add(time.Hour*24),
|
||||
domain.WeatherConditionClear,
|
||||
domain.Temperature{Value: domain.Celsius(20)},
|
||||
),
|
||||
}, nil)
|
||||
flow := flow.NewFlow(mock)
|
||||
|
||||
weathers, err := flow.ListDailyWeathers(context.Background(), "Asia/Seoul", now)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, weathers, 1)
|
||||
require.InDelta(t, 20.0, weathers[0].High, 0.01)
|
||||
require.InDelta(t, 20.0, weathers[0].Low, 0.01)
|
||||
require.Equal(t, "CLEAR", weathers[0].Condition)
|
||||
}
|
||||
|
||||
func TestFlow_ListDailyWeathers_SameSourceAndTargetDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mocks.NewMockRepository(ctrl)
|
||||
now := time.Now()
|
||||
mock.EXPECT().ListWeathers(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*domain.Weather{
|
||||
domain.NewWeather(
|
||||
"1",
|
||||
domain.WeatherSourceOpenWeather,
|
||||
now,
|
||||
now,
|
||||
domain.WeatherConditionClear,
|
||||
domain.Temperature{Value: domain.Celsius(20)},
|
||||
),
|
||||
domain.NewWeather(
|
||||
"2",
|
||||
domain.WeatherSourceOpenWeather,
|
||||
now,
|
||||
now.Add(time.Hour*24),
|
||||
domain.WeatherConditionCloudy,
|
||||
domain.Temperature{Value: domain.Celsius(30)},
|
||||
),
|
||||
}, nil)
|
||||
flow := flow.NewFlow(mock)
|
||||
|
||||
weathers, err := flow.ListDailyWeathers(context.Background(), "Asia/Seoul", time.Now())
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, weathers, 1)
|
||||
require.InDelta(t, 30.0, weathers[0].High, 0.01)
|
||||
require.InDelta(t, 30.0, weathers[0].Low, 0.01)
|
||||
require.Equal(t, "CLOUDY", weathers[0].Condition)
|
||||
}
|
||||
|
||||
func TestFlow_ListDailyWeathers_DifferentSourceAndTargetDate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
mock := mocks.NewMockRepository(ctrl)
|
||||
now := time.Now()
|
||||
mock.EXPECT().ListWeathers(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*domain.Weather{
|
||||
domain.NewWeather(
|
||||
"1",
|
||||
domain.WeatherSourceOpenWeather,
|
||||
now,
|
||||
now,
|
||||
domain.WeatherConditionClear,
|
||||
domain.Temperature{Value: domain.Celsius(20)},
|
||||
),
|
||||
domain.NewWeather(
|
||||
"2",
|
||||
domain.WeatherSourceKMA,
|
||||
now,
|
||||
now.Add(time.Hour*24),
|
||||
domain.WeatherConditionCloudy,
|
||||
domain.Temperature{Value: domain.Celsius(30)},
|
||||
),
|
||||
}, nil)
|
||||
flow := flow.NewFlow(mock)
|
||||
|
||||
weathers, err := flow.ListDailyWeathers(context.Background(), "Asia/Seoul", time.Now())
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, weathers, 1)
|
||||
require.InDelta(t, 30.0, weathers[0].High, 0.01)
|
||||
require.InDelta(t, 20.0, weathers[0].Low, 0.01)
|
||||
require.Equal(t, "CLOUDY", weathers[0].Condition)
|
||||
}
|
||||
120
internal/pkg/repository/mocks/repository.go
Normal file
120
internal/pkg/repository/mocks/repository.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/neatflowcv/seven-skies/internal/pkg/repository (interfaces: Repository)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -typed -package=mocks -destination=mocks/repository.go . Repository
|
||||
//
|
||||
|
||||
// Package mocks is a generated GoMock package.
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
reflect "reflect"
|
||||
time "time"
|
||||
|
||||
domain "github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockRepository is a mock of Repository interface.
|
||||
type MockRepository struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockRepositoryMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockRepositoryMockRecorder is the mock recorder for MockRepository.
|
||||
type MockRepositoryMockRecorder struct {
|
||||
mock *MockRepository
|
||||
}
|
||||
|
||||
// NewMockRepository creates a new mock instance.
|
||||
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
|
||||
mock := &MockRepository{ctrl: ctrl}
|
||||
mock.recorder = &MockRepositoryMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// CreateWeather mocks base method.
|
||||
func (m *MockRepository) CreateWeather(ctx context.Context, weather *domain.Weather) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "CreateWeather", ctx, weather)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// CreateWeather indicates an expected call of CreateWeather.
|
||||
func (mr *MockRepositoryMockRecorder) CreateWeather(ctx, weather any) *MockRepositoryCreateWeatherCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateWeather", reflect.TypeOf((*MockRepository)(nil).CreateWeather), ctx, weather)
|
||||
return &MockRepositoryCreateWeatherCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRepositoryCreateWeatherCall wrap *gomock.Call
|
||||
type MockRepositoryCreateWeatherCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRepositoryCreateWeatherCall) Return(arg0 error) *MockRepositoryCreateWeatherCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRepositoryCreateWeatherCall) Do(f func(context.Context, *domain.Weather) error) *MockRepositoryCreateWeatherCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRepositoryCreateWeatherCall) DoAndReturn(f func(context.Context, *domain.Weather) error) *MockRepositoryCreateWeatherCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// ListWeathers mocks base method.
|
||||
func (m *MockRepository) ListWeathers(ctx context.Context, from, to time.Time) ([]*domain.Weather, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ListWeathers", ctx, from, to)
|
||||
ret0, _ := ret[0].([]*domain.Weather)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ListWeathers indicates an expected call of ListWeathers.
|
||||
func (mr *MockRepositoryMockRecorder) ListWeathers(ctx, from, to any) *MockRepositoryListWeathersCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListWeathers", reflect.TypeOf((*MockRepository)(nil).ListWeathers), ctx, from, to)
|
||||
return &MockRepositoryListWeathersCall{Call: call}
|
||||
}
|
||||
|
||||
// MockRepositoryListWeathersCall wrap *gomock.Call
|
||||
type MockRepositoryListWeathersCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockRepositoryListWeathersCall) Return(arg0 []*domain.Weather, arg1 error) *MockRepositoryListWeathersCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockRepositoryListWeathersCall) Do(f func(context.Context, time.Time, time.Time) ([]*domain.Weather, error)) *MockRepositoryListWeathersCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockRepositoryListWeathersCall) DoAndReturn(f func(context.Context, time.Time, time.Time) ([]*domain.Weather, error)) *MockRepositoryListWeathersCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
||||
)
|
||||
|
||||
//go:generate mockgen -typed -package=mocks -destination=mocks/repository.go . Repository
|
||||
|
||||
type Repository interface {
|
||||
CreateWeather(ctx context.Context, weather *domain.Weather) error
|
||||
ListWeathers(ctx context.Context, from, to time.Time) ([]*domain.Weather, error)
|
||||
|
||||
Reference in New Issue
Block a user