package main import ( "fmt" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/require" ) func TestWhatsAppBatchesFollowTheTimesOfTheMessages(t *testing.T) { testCases := []struct { name string times []time.Duration sizes []int }{ { name: "messages close together form one batch", times: []time.Duration{0, 10 * time.Second, 30 * time.Second}, sizes: []int{3}, }, { name: "a quiet chat starts a new batch", times: []time.Duration{0, 10 * time.Second, 31 * time.Second}, sizes: []int{2, 1}, }, { name: "a batch ends after two minutes", times: []time.Duration{0, 20 * time.Second, 40 * time.Second, 60 * time.Second, 80 * time.Second, 100 * time.Second, 120 * time.Second, 140 * time.Second}, sizes: []int{7, 1}, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName)) start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC) for index, offset := range tc.times { _, err := batches.Add(whatsappTestChat, WhatsAppMessage{ ID: string(rune('a' + index)), Time: start.Add(offset), Text: "hi", }, time.Now()) require.NoError(t, err) } require.NoError(t, batches.CloseAll()) var sizes []int for _, batch := range batches.Closed() { sizes = append(sizes, len(batch.Messages)) } require.Equal(t, tc.sizes, sizes) }) } } func TestWhatsAppBatchClosesAtFiftyMessages(t *testing.T) { batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName)) start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC) for index := range whatsappBatchMessages { closed, err := batches.Add(whatsappTestChat, WhatsAppMessage{ ID: time.Duration(index).String(), Time: start, Text: "hi", }, time.Now()) require.NoError(t, err) require.Equal(t, index == whatsappBatchMessages-1, closed) } require.Len(t, batches.Closed(), 1) } func TestWhatsAppBatchClosesInRealTime(t *testing.T) { batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName)) arrival := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC) for index, offset := range []time.Duration{0, 15 * time.Second, 30 * time.Second} { _, err := batches.Add(whatsappTestChat, WhatsAppMessage{ ID: string(rune('a' + index)), Time: arrival, Text: "hi", }, arrival.Add(offset)) require.NoError(t, err) } wait, open := batches.Wait(arrival.Add(40 * time.Second)) require.True(t, open) require.Equal(t, 10*time.Second, wait) closed, err := batches.CloseDue(arrival.Add(49 * time.Second)) require.NoError(t, err) require.False(t, closed) closed, err = batches.CloseDue(arrival.Add(50 * time.Second)) require.NoError(t, err) require.True(t, closed) _, open = batches.Wait(arrival.Add(50 * time.Second)) require.False(t, open) } func TestWhatsAppBatchesSurviveARestart(t *testing.T) { path := filepath.Join(t.TempDir(), whatsappDirName, whatsappStateFileName) batches := whatsappTestState(t, path) for _, id := range []string{"a", "a", "b"} { _, err := batches.Add(whatsappTestChat, WhatsAppMessage{ ID: id, Time: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC), Text: "hi " + id, }, time.Now()) require.NoError(t, err) } info, err := os.Stat(path) require.NoError(t, err) require.Equal(t, os.FileMode(0o600), info.Mode().Perm()) again := whatsappTestState(t, path) require.NoError(t, again.CloseAll()) closed := again.Closed() require.Len(t, closed, 1) require.Equal(t, []string{"hi a", "hi b"}, []string{closed[0].Messages[0].Text, closed[0].Messages[1].Text}) require.NoError(t, again.Remove(closed[0].Number)) require.Empty(t, whatsappTestState(t, path).Closed()) } func TestWhatsAppStateRefusesABrokenFile(t *testing.T) { path := filepath.Join(t.TempDir(), whatsappStateFileName) require.NoError(t, os.WriteFile(path, []byte("{"), 0o600)) _, err := openWhatsAppState(path) require.ErrorContains(t, err, path) } func TestWhatsAppStateKeepsTheLastSendRecords(t *testing.T) { state := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName)) for index := range whatsappSendRecords + 1 { require.NoError(t, state.PutSend(WhatsAppSend{ ID: fmt.Sprint(index), State: whatsappSent, })) } _, found := state.Send("0") require.False(t, found) _, found = state.Send(fmt.Sprint(whatsappSendRecords)) require.True(t, found) } // whatsappTestState opens the batches of the state file at path with the // times of the plan. func whatsappTestState(t *testing.T, path string) *WhatsAppState { t.Helper() batches, err := openWhatsAppState(path) require.NoError(t, err) return batches }