]> git.giacomolli.uk - onechat/blob - whatsapp_state_test.go
Shorten user service updates
[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 var sizes []int
51 for _, batch := range batches.Closed() {
52 sizes = append(sizes, len(batch.Messages))
53 }
54
55 require.Equal(t, tc.sizes, sizes)
56 })
57 }
58 }
59
60 func TestWhatsAppBatchClosesAtFiftyMessages(t *testing.T) {
61 batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName))
62 start := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
63 for index := range whatsappBatchMessages {
64 closed, err := batches.Add(whatsappTestChat, WhatsAppMessage{
65 ID: time.Duration(index).String(),
66 Time: start,
67 Text: "hi",
68 }, time.Now())
69 require.NoError(t, err)
70 require.Equal(t, index == whatsappBatchMessages-1, closed)
71 }
72
73 require.Len(t, batches.Closed(), 1)
74 }
75
76 func TestWhatsAppBatchClosesInRealTime(t *testing.T) {
77 batches := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName))
78 arrival := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
79 for index, offset := range []time.Duration{0, 15 * time.Second, 30 * time.Second} {
80 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
81 ID: string(rune('a' + index)),
82 Time: arrival,
83 Text: "hi",
84 }, arrival.Add(offset))
85 require.NoError(t, err)
86 }
87
88 wait, open := batches.Wait(arrival.Add(40 * time.Second))
89 require.True(t, open)
90 require.Equal(t, 10*time.Second, wait)
91
92 closed, err := batches.CloseDue(arrival.Add(49 * time.Second))
93 require.NoError(t, err)
94 require.False(t, closed)
95
96 closed, err = batches.CloseDue(arrival.Add(50 * time.Second))
97 require.NoError(t, err)
98 require.True(t, closed)
99
100 _, open = batches.Wait(arrival.Add(50 * time.Second))
101 require.False(t, open)
102 }
103
104 func TestWhatsAppBatchesSurviveARestart(t *testing.T) {
105 path := filepath.Join(t.TempDir(), whatsappDirName, whatsappStateFileName)
106 batches := whatsappTestState(t, path)
107 for _, id := range []string{"a", "a", "b"} {
108 _, err := batches.Add(whatsappTestChat, WhatsAppMessage{
109 ID: id,
110 Time: time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC),
111 Text: "hi " + id,
112 }, time.Now())
113 require.NoError(t, err)
114 }
115
116 info, err := os.Stat(path)
117 require.NoError(t, err)
118 require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
119
120 again := whatsappTestState(t, path)
121 require.NoError(t, again.CloseAll())
122 closed := again.Closed()
123 require.Len(t, closed, 1)
124 require.Equal(t, []string{"hi a", "hi b"}, []string{closed[0].Messages[0].Text, closed[0].Messages[1].Text})
125
126 require.NoError(t, again.Remove(closed[0].Number))
127 require.Empty(t, whatsappTestState(t, path).Closed())
128 }
129
130 func TestWhatsAppStateRefusesABrokenFile(t *testing.T) {
131 path := filepath.Join(t.TempDir(), whatsappStateFileName)
132 require.NoError(t, os.WriteFile(path, []byte("{"), 0o600))
133
134 _, err := openWhatsAppState(path, whatsappBatchQuiet, whatsappBatchWindow)
135 require.ErrorContains(t, err, path)
136 }
137
138 func TestWhatsAppStateKeepsTheLastSendRecords(t *testing.T) {
139 state := whatsappTestState(t, filepath.Join(t.TempDir(), whatsappStateFileName))
140 for index := range whatsappSendRecords + 1 {
141 require.NoError(t, state.PutSend(WhatsAppSend{
142 ID: fmt.Sprint(index),
143 State: whatsappSent,
144 }))
145 }
146
147 _, found := state.Send("0")
148 require.False(t, found)
149
150 _, found = state.Send(fmt.Sprint(whatsappSendRecords))
151 require.True(t, found)
152 }
153
154 // whatsappTestState opens the batches of the state file at path with the
155 // times of the plan.
156 func whatsappTestState(t *testing.T, path string) *WhatsAppState {
157 t.Helper()
158
159 batches, err := openWhatsAppState(path, whatsappBatchQuiet, whatsappBatchWindow)
160 require.NoError(t, err)
161
162 return batches
163 }