package daemon_test import ( "context" "errors" "testing" "time" "git.netflux.io/rob/elon-eats-my-tweets/daemon" "git.netflux.io/rob/elon-eats-my-tweets/generated/mocks" "git.netflux.io/rob/elon-eats-my-tweets/generated/store" "git.netflux.io/rob/elon-eats-my-tweets/twitterapi" "github.com/jackc/pgx/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "go.uber.org/zap" ) func TestTweetUpdaterUpdateTweetsInitialTweet(t *testing.T) { utcLoc, _ := time.LoadLocation("UTC") createdAt := time.Date(2022, 1, 1, 12, 12, 23, 0, utcLoc) testCases := []struct { name string lastKnownTweetErr error fetchedTweet *twitterapi.Tweet fetchedTweetErr error insertTweetErr error wantCount int wantErr string }{ { name: "error fetching last known tweet", lastKnownTweetErr: errors.New("database error"), wantCount: 0, wantErr: "error fetching last Elon tweet: database error", }, { name: "no tweet available from the API", lastKnownTweetErr: pgx.ErrNoRows, fetchedTweetErr: twitterapi.ErrNoTweets, wantCount: 0, wantErr: "", }, { name: "error fetching tweet from API", lastKnownTweetErr: pgx.ErrNoRows, fetchedTweetErr: errors.New("API error"), wantCount: 0, wantErr: "error fetching initial tweet: API error", }, { name: "error inserting tweet", lastKnownTweetErr: pgx.ErrNoRows, fetchedTweet: &twitterapi.Tweet{ID: "101", Text: "bar", CreatedAt: createdAt}, insertTweetErr: errors.New("boom"), wantCount: 0, wantErr: "error inserting initial tweet: error upserting tweet: boom", }, { name: "success", lastKnownTweetErr: pgx.ErrNoRows, fetchedTweet: &twitterapi.Tweet{ID: "101", Text: "bar", CreatedAt: createdAt}, wantCount: 1, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var mockStore mocks.Store mockStore.On("GetLastElonTweet", mock.Anything).Return(store.ElonTweet{}, tc.lastKnownTweetErr) mockStore. On("UpsertElonTweet", mock.Anything, mock.MatchedBy(func(params store.UpsertElonTweetParams) bool { return params.TwitterID == 101 && params.Text == "bar" && params.PostedAt.Equal(createdAt) && params.ProcessedAt.Valid })). Return(store.ElonTweet{}, tc.insertTweetErr) var mockAPIClient mocks.TwitterAPIClient mockAPIClient.On("GetLastTweet", "44196397").Return(tc.fetchedTweet, tc.fetchedTweetErr) if tc.wantErr == "" { defer mockAPIClient.AssertExpectations(t) } updater := daemon.NewTweetPersister(&mockStore, &mockAPIClient, zap.NewNop().Sugar()) n, err := updater.PersistTweets(context.Background()) assert.Equal(t, tc.wantCount, n) if tc.wantErr == "" { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.wantErr) } }) } } func TestTweetUpdaterUpdateTweets(t *testing.T) { utcLoc, _ := time.LoadLocation("UTC") createdAt := time.Date(2022, 1, 1, 12, 12, 23, 0, utcLoc) lastKnownTweet := store.ElonTweet{TwitterID: 100, Text: "foo", PostedAt: createdAt, CreatedAt: createdAt} testCases := []struct { name string lastKnownTweetErr error fetchedTweets []*twitterapi.Tweet fetchedTweetsErr error insertTweetErr error wantCount int wantErr string }{ { name: "error fetching tweets", fetchedTweetsErr: errors.New("whale"), wantCount: 0, wantErr: "error fetching latest Elon tweets: whale", }, { name: "error inserting tweet", fetchedTweets: []*twitterapi.Tweet{{ID: "101", Text: "bar", CreatedAt: createdAt}}, insertTweetErr: errors.New("database error"), wantCount: 0, wantErr: "error inserting tweet: error upserting tweet: database error", }, { name: "success", fetchedTweets: []*twitterapi.Tweet{ {ID: "101", Text: "bar", CreatedAt: createdAt}, {ID: "102", Text: "baz", CreatedAt: createdAt}, {ID: "103", Text: "qux", CreatedAt: createdAt}, }, wantCount: 3, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var mockStore mocks.Store mockStore.On("GetLastElonTweet", mock.Anything).Return(lastKnownTweet, nil) mockStore. On("UpsertElonTweet", mock.Anything, mock.MatchedBy(func(params store.UpsertElonTweetParams) bool { return params.TwitterID == 101 && params.Text == "bar" && params.PostedAt.Equal(createdAt) && !params.ProcessedAt.Valid })). Return(store.ElonTweet{}, tc.insertTweetErr) mockStore. On("UpsertElonTweet", mock.Anything, mock.MatchedBy(func(params store.UpsertElonTweetParams) bool { return params.TwitterID == 102 && params.Text == "baz" && params.PostedAt.Equal(createdAt) && !params.ProcessedAt.Valid })). Return(store.ElonTweet{}, tc.insertTweetErr) mockStore. On("UpsertElonTweet", mock.Anything, mock.MatchedBy(func(params store.UpsertElonTweetParams) bool { return params.TwitterID == 103 && params.Text == "qux" && params.PostedAt.Equal(createdAt) && !params.ProcessedAt.Valid })). Return(store.ElonTweet{}, tc.insertTweetErr) if tc.wantErr == "" { defer mockStore.AssertExpectations(t) } var mockAPIClient mocks.TwitterAPIClient mockAPIClient.On("GetTweets", "44196397", "100").Return(tc.fetchedTweets, tc.fetchedTweetsErr) if tc.wantErr == "" { defer mockAPIClient.AssertExpectations(t) } updater := daemon.NewTweetPersister(&mockStore, &mockAPIClient, zap.NewNop().Sugar()) n, err := updater.PersistTweets(context.Background()) assert.Equal(t, tc.wantCount, n) if tc.wantErr == "" { assert.NoError(t, err) } else { assert.EqualError(t, err, tc.wantErr) } }) } }