]> git.giacomolli.uk - onechat/blob - whatsapp_state_test.go
Import WhatsApp history into a child
[onechat] / whatsapp_state_test.go
1 package main
2
3 import (
4         "fmt"
5         "os"
6         "path/filepath"
7         "testing"
8         "time"
9
10         "github.com/stretchr/testify/require"
11 )
12
13 func TestWhatsAppBatchesFollowTheTimesOfTheMessages(t *testing.T) {
14         testCases := []struct {
15                 name  string
16                 times []time.Duration
17                 sizes []int
18         }{
19                 {
20                         name:  "messages close together form one batch",
21                         times: []time.Duration{0, 10 * time.Second, 30 * time.Second},
22                         sizes: []int{3},
23                 },
24                 {
25                         name:  "a quiet chat starts a new batch",
26                         times: []time.Duration{0, 10 * time.Second, 31 * time.Second},
27                         sizes: []int{2, 1},
28                 },
29                 {
30                         name:  "a batch ends after two minutes",
31                         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},
32                         sizes: []int{7, 1},
33                 },
34         }
35
36         for _, tc := range testCases {
37                 t.Run(tc.name, func(t *testing.T) {
38                         batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName))
39                         start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
40                         for index, offset := range tc.times {
41                                 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
42                                         ID:   string(rune('a' + index)),
43                                         Time: start.Add(offset),
44                                         Text: "hi",
45                                 }, time.Now())
46                                 require.NoError(t, err)
47                         }
48
49                         require.NoError(t, batches.CloseAll())
50
51                         var sizes []int
52
53                         for _, batch := range batches.Closed() {
54                                 sizes = append(sizes, len(batch.Messages))
55                         }
56
57                         require.Equal(t, tc.sizes, sizes)
58                 })
59         }
60 }
61
62 func TestWhatsAppBatchClosesAtFiftyMessages(t *testing.T) {
63         batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName))
64         start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
65         for index := range whatsappBatchMessages {
66                 closed, err := batches.Add(whatsappTestChat, WhatsAppMessage{
67                         ID:   time.Duration(index).String(),
68                         Time: start,
69                         Text: "hi",
70                 }, time.Now())
71                 require.NoError(t, err)
72                 require.Equal(t, index == whatsappBatchMessages-1, closed)
73         }
74
75         require.Len(t, batches.Closed(), 1)
76 }
77
78 func TestWhatsAppBatchClosesInRealTime(t *testing.T) {
79         batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName))
80         arrival := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
81         for index, offset := range []time.Duration{0, 15 * time.Second, 30 * time.Second} {
82                 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
83                         ID:   string(rune('a' + index)),
84                         Time: arrival,
85                         Text: "hi",
86                 }, arrival.Add(offset))
87                 require.NoError(t, err)
88         }
89
90         wait, open := batches.Wait(arrival.Add(40 * time.Second))
91         require.True(t, open)
92         require.Equal(t, 10*time.Second, wait)
93
94         closed, err := batches.CloseDue(arrival.Add(49 * time.Second))
95         require.NoError(t, err)
96         require.False(t, closed)
97
98         closed, err = batches.CloseDue(arrival.Add(50 * time.Second))
99         require.NoError(t, err)
100         require.True(t, closed)
101
102         _, open = batches.Wait(arrival.Add(50 * time.Second))
103         require.False(t, open)
104 }
105
106 func TestWhatsAppBatchesSurviveARestart(t *testing.T) {
107         path := filepath.Join(t.TempDir(), whatsappDirName, whatsappStateFileName)
108         batches := whatsappTestState(t, path)
109         for _, id := range []string{"a", "a", "b"} {
110                 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
111                         ID:   id,
112                         Time: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC),
113                         Text: "hi " + id,
114                 }, time.Now())
115                 require.NoError(t, err)
116         }
117
118         info, err := os.Stat(path)
119         require.NoError(t, err)
120         require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
121
122         again := whatsappTestState(t, path)
123         require.NoError(t, again.CloseAll())
124
125         closed := again.Closed()
126         require.Len(t, closed, 1)
127         require.Equal(t, []string{"hi a", "hi b"}, []string{closed[0].Messages[0].Text, closed[0].Messages[1].Text})
128
129         require.NoError(t, again.Remove(closed[0].Number))
130         require.Empty(t, whatsappTestState(t, path).Closed())
131 }
132
133 func TestWhatsAppStateRefusesABrokenFile(t *testing.T) {
134         path := filepath.Join(t.TempDir(), whatsappStateFileName)
135         require.NoError(t, os.WriteFile(path, []byte("{"), 0o600))
136
137         _, err := openWhatsAppState(path, whatsappBatchQuiet, whatsappBatchWindow)
138         require.ErrorContains(t, err, path)
139 }
140
141 func TestWhatsAppStateKeepsTheLastSendRecords(t *testing.T) {
142         state := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName))
143         for index := range whatsappSendRecords + 1 {
144                 require.NoError(t, state.PutSend(WhatsAppSend{
145                         ID:    fmt.Sprint(index),
146                         State: whatsappSent,
147                 }))
148         }
149
150         _, found := state.Send("0")
151         require.False(t, found)
152
153         _, found = state.Send(fmt.Sprint(whatsappSendRecords))
154         require.True(t, found)
155 }
156
157 // whatsappTestState opens the batches of the state file at path with the
158 // times of the plan.
159 func whatsappTestState(t *testing.T, path string) *WhatsAppState {
160         t.Helper()
161
162         batches, err := openWhatsAppState(path, whatsappBatchQuiet, whatsappBatchWindow)
163         require.NoError(t, err)
164
165         return batches
166 }