]> git.giacomolli.uk - onechat/blob - whatsapp_fake_helper_test.go
1a7130c9b21099832180d9fc996c5576eaff9fde
[onechat] / whatsapp_fake_helper_test.go
1 package main
2
3 import (
4 "context"
5 "io"
6 "log"
7 "os"
8 "path/filepath"
9 "sync"
10 "testing"
11
12 "github.com/stretchr/testify/require"
13 "go.mau.fi/whatsmeow"
14 waAdv "go.mau.fi/whatsmeow/proto/waAdv"
15 "go.mau.fi/whatsmeow/store"
16 "go.mau.fi/whatsmeow/store/sqlstore"
17 "go.mau.fi/whatsmeow/types"
18 waLog "go.mau.fi/whatsmeow/util/log"
19 )
20
21 const (
22 // whatsappTestNumber is the phone number of the own account in the
23 // tests. It is a number for fiction.
24 whatsappTestNumber = "15550001111"
25
26 // whatsappTestLID is the LID of the own account in the tests.
27 whatsappTestLID = "987654321012345"
28
29 // whatsappTestCode is the pairing code of the fake.
30 whatsappTestCode = "ABCD-EFGH"
31 )
32
33 // WhatsAppFake is a fake WhatsApp client. It keeps the event handler that the
34 // daemon gives it, and Emit gives events to the handler, as the library does.
35 // Each connection sends to connects, and each pairing sends its channel to
36 // pairings, so that the test gives the items of the pairing. A connection
37 // fails with connectErr, and a logout with logoutErr.
38 type WhatsAppFake struct {
39 connects chan struct{}
40 pairings chan chan whatsmeow.QRChannelItem
41
42 mutex sync.Mutex
43 handle func(event any) bool
44 connectErr error
45 logoutErr error
46 phones []string
47 logouts int
48 }
49
50 // ConnectContext tells the test that the daemon connected.
51 func (f *WhatsAppFake) ConnectContext(ctx context.Context) error {
52 f.connects <- struct{}{}
53
54 f.mutex.Lock()
55 defer f.mutex.Unlock()
56
57 return f.connectErr
58 }
59
60 // Disconnect does nothing.
61 func (f *WhatsAppFake) Disconnect() {
62 }
63
64 // GetQRChannel gives a new channel of pairing items to the test.
65 func (f *WhatsAppFake) GetQRChannel(ctx context.Context) (<-chan whatsmeow.QRChannelItem, error) {
66 items := make(chan whatsmeow.QRChannelItem, 8)
67 f.pairings <- items
68
69 return items, nil
70 }
71
72 // PairPhone keeps phone and returns whatsappTestCode.
73 func (f *WhatsAppFake) PairPhone(ctx context.Context, phone string, showPushNotification bool, clientType whatsmeow.PairClientType, clientDisplayName string) (string, error) {
74 f.mutex.Lock()
75 defer f.mutex.Unlock()
76
77 f.phones = append(f.phones, phone)
78
79 return whatsappTestCode, nil
80 }
81
82 // Logout counts the logouts and returns logoutErr.
83 func (f *WhatsAppFake) Logout(ctx context.Context) error {
84 f.mutex.Lock()
85 defer f.mutex.Unlock()
86
87 f.logouts++
88
89 return f.logoutErr
90 }
91
92 // Phones returns the phone numbers of the pairings.
93 func (f *WhatsAppFake) Phones() []string {
94 f.mutex.Lock()
95 defer f.mutex.Unlock()
96
97 return append([]string{}, f.phones...)
98 }
99
100 // Logouts returns the number of logouts.
101 func (f *WhatsAppFake) Logouts() int {
102 f.mutex.Lock()
103 defer f.mutex.Unlock()
104
105 return f.logouts
106 }
107
108 // Emit gives event to the handler of the daemon and returns its answer.
109 func (f *WhatsAppFake) Emit(event any) bool {
110 f.mutex.Lock()
111 handle := f.handle
112 f.mutex.Unlock()
113
114 return handle(event)
115 }
116
117 // newClient keeps handle and returns the fake as the client of device.
118 func (f *WhatsAppFake) newClient(device *store.Device, handle func(event any) bool) WhatsAppClient {
119 f.mutex.Lock()
120 defer f.mutex.Unlock()
121
122 f.handle = handle
123
124 return f
125 }
126
127 // whatsappStartTest opens the WhatsApp of the data directory dir with a fake
128 // client and the test clock, and closes it at the end of the test. The daemon
129 // log goes to the returned buffer.
130 func whatsappStartTest(t *testing.T, dir string, clock *TestClock) (*WhatsApp, *WhatsAppFake, *SyncBuffer) {
131 t.Helper()
132
133 fake := &WhatsAppFake{
134 connects: make(chan struct{}, 16),
135 pairings: make(chan chan whatsmeow.QRChannelItem, 16),
136 }
137
138 logs := &SyncBuffer{}
139 whatsapp, err := openWhatsApp(t.Context(), dir, clock.Now, log.New(logs, "", 0), fake.newClient)
140 require.NoError(t, err)
141
142 t.Cleanup(func() {
143 require.NoError(t, whatsapp.Close())
144 })
145
146 return whatsapp, fake, logs
147 }
148
149 // whatsappTestSession writes a session database with mode 0600 to the data
150 // directory dir. When linked is true, it holds a device linked to
151 // whatsappTestNumber and whatsappTestLID, and else no device.
152 func whatsappTestSession(t *testing.T, dir string, linked bool) {
153 t.Helper()
154
155 path := filepath.Join(dir, whatsappDirName, whatsappSessionFileName)
156 require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700))
157 require.NoError(t, os.WriteFile(path, nil, 0o600))
158
159 address, err := whatsappSessionAddress(path)
160 require.NoError(t, err)
161
162 container, err := sqlstore.New(t.Context(), "sqlite", address, waLog.Noop)
163 require.NoError(t, err)
164
165 if linked {
166 device := container.NewDevice()
167 jid := types.NewADJID(whatsappTestNumber, 0, 1)
168 device.ID = &jid
169 device.LID = types.NewADJID(whatsappTestLID, 0, 1)
170 device.LID.Server = types.HiddenUserServer
171 device.Account = &waAdv.ADVSignedDeviceIdentity{
172 Details: []byte{},
173 AccountSignature: make([]byte, 64),
174 AccountSignatureKey: make([]byte, 32),
175 DeviceSignature: make([]byte, 64),
176 }
177 require.NoError(t, device.Save(t.Context()))
178 }
179
180 require.NoError(t, container.Close())
181 }
182
183 // whatsappDiscardLog is a logger for a WhatsApp whose log a test does not
184 // read.
185 func whatsappDiscardLog() *log.Logger {
186 return log.New(io.Discard, "", 0)
187 }