1//go:build !integration
2
3package main
4
5import (
6 "context"
7 "flag"
8 "net"
9 "os"
10 "path/filepath"
11 "testing"
12
13 "github.com/mjl-/mox/dmarcdb"
14 "github.com/mjl-/mox/dns"
15 "github.com/mjl-/mox/mlog"
16 "github.com/mjl-/mox/mox-"
17 "github.com/mjl-/mox/mtastsdb"
18 "github.com/mjl-/mox/queue"
19 "github.com/mjl-/mox/store"
20 "github.com/mjl-/mox/tlsrptdb"
21)
22
23var ctxbg = context.Background()
24var pkglog = mlog.New("ctl", nil)
25
26func tcheck(t *testing.T, err error, errmsg string) {
27 if err != nil {
28 t.Helper()
29 t.Fatalf("%s: %v", errmsg, err)
30 }
31}
32
33// TestCtl executes commands through ctl. This tests at least the protocols (who
34// sends when/what) is tested. We often don't check the actual results, but
35// unhandled errors would cause a panic.
36func TestCtl(t *testing.T) {
37 os.RemoveAll("testdata/ctl/data")
38 mox.ConfigStaticPath = filepath.FromSlash("testdata/ctl/mox.conf")
39 mox.ConfigDynamicPath = filepath.FromSlash("testdata/ctl/domains.conf")
40 if errs := mox.LoadConfig(ctxbg, pkglog, true, false); len(errs) > 0 {
41 t.Fatalf("loading mox config: %v", errs)
42 }
43 defer store.Switchboard()()
44
45 testctl := func(fn func(clientctl *ctl)) {
46 t.Helper()
47
48 cconn, sconn := net.Pipe()
49 clientctl := ctl{conn: cconn, log: pkglog}
50 serverctl := ctl{conn: sconn, log: pkglog}
51 go servectlcmd(ctxbg, &serverctl, func() {})
52 fn(&clientctl)
53 cconn.Close()
54 sconn.Close()
55 }
56
57 // "deliver"
58 testctl(func(ctl *ctl) {
59 ctlcmdDeliver(ctl, "mjl@mox.example")
60 })
61
62 // "setaccountpassword"
63 testctl(func(ctl *ctl) {
64 ctlcmdSetaccountpassword(ctl, "mjl", "test4321")
65 })
66
67 err := queue.Init()
68 tcheck(t, err, "queue init")
69
70 // "queue"
71 testctl(func(ctl *ctl) {
72 ctlcmdQueueList(ctl)
73 })
74
75 // "queuekick"
76 testctl(func(ctl *ctl) {
77 ctlcmdQueueKick(ctl, 0, "", "", "")
78 })
79
80 // "queuedrop"
81 testctl(func(ctl *ctl) {
82 ctlcmdQueueDrop(ctl, 0, "", "")
83 })
84
85 // no "queuedump", we don't have a message to dump, and the commands exits without a message.
86
87 // "importmbox"
88 testctl(func(ctl *ctl) {
89 ctlcmdImport(ctl, true, "mjl", "inbox", "testdata/importtest.mbox")
90 })
91
92 // "importmaildir"
93 testctl(func(ctl *ctl) {
94 ctlcmdImport(ctl, false, "mjl", "inbox", "testdata/importtest.maildir")
95 })
96
97 // "domainadd"
98 testctl(func(ctl *ctl) {
99 ctlcmdConfigDomainAdd(ctl, dns.Domain{ASCII: "mox2.example"}, "mjl", "")
100 })
101
102 // "accountadd"
103 testctl(func(ctl *ctl) {
104 ctlcmdConfigAccountAdd(ctl, "mjl2", "mjl2@mox2.example")
105 })
106
107 // "addressadd"
108 testctl(func(ctl *ctl) {
109 ctlcmdConfigAddressAdd(ctl, "mjl3@mox2.example", "mjl2")
110 })
111
112 // Add a message.
113 testctl(func(ctl *ctl) {
114 ctlcmdDeliver(ctl, "mjl3@mox2.example")
115 })
116 // "retrain", retrain junk filter.
117 testctl(func(ctl *ctl) {
118 ctlcmdRetrain(ctl, "mjl2")
119 })
120
121 // "addressrm"
122 testctl(func(ctl *ctl) {
123 ctlcmdConfigAddressRemove(ctl, "mjl3@mox2.example")
124 })
125
126 // "accountrm"
127 testctl(func(ctl *ctl) {
128 ctlcmdConfigAccountRemove(ctl, "mjl2")
129 })
130
131 // "domainrm"
132 testctl(func(ctl *ctl) {
133 ctlcmdConfigDomainRemove(ctl, dns.Domain{ASCII: "mox2.example"})
134 })
135
136 // "loglevels"
137 testctl(func(ctl *ctl) {
138 ctlcmdLoglevels(ctl)
139 })
140
141 // "setloglevels"
142 testctl(func(ctl *ctl) {
143 ctlcmdSetLoglevels(ctl, "", "debug")
144 })
145 testctl(func(ctl *ctl) {
146 ctlcmdSetLoglevels(ctl, "smtpserver", "debug")
147 })
148
149 // Export data, import it again
150 xcmdExport(true, []string{filepath.FromSlash("testdata/ctl/data/tmp/export/mbox/"), filepath.FromSlash("testdata/ctl/data/accounts/mjl")}, &cmd{log: pkglog})
151 xcmdExport(false, []string{filepath.FromSlash("testdata/ctl/data/tmp/export/maildir/"), filepath.FromSlash("testdata/ctl/data/accounts/mjl")}, &cmd{log: pkglog})
152 testctl(func(ctl *ctl) {
153 ctlcmdImport(ctl, true, "mjl", "inbox", filepath.FromSlash("testdata/ctl/data/tmp/export/mbox/Inbox.mbox"))
154 })
155 testctl(func(ctl *ctl) {
156 ctlcmdImport(ctl, false, "mjl", "inbox", filepath.FromSlash("testdata/ctl/data/tmp/export/maildir/Inbox"))
157 })
158
159 // "recalculatemailboxcounts"
160 testctl(func(ctl *ctl) {
161 ctlcmdRecalculateMailboxCounts(ctl, "mjl")
162 })
163
164 // "fixmsgsize"
165 testctl(func(ctl *ctl) {
166 ctlcmdFixmsgsize(ctl, "mjl")
167 })
168 testctl(func(ctl *ctl) {
169 acc, err := store.OpenAccount(ctl.log, "mjl")
170 tcheck(t, err, "open account")
171 defer acc.Close()
172
173 content := []byte("Subject: hi\r\n\r\nbody\r\n")
174
175 deliver := func(m *store.Message) {
176 t.Helper()
177 m.Size = int64(len(content))
178 msgf, err := store.CreateMessageTemp(ctl.log, "ctltest")
179 tcheck(t, err, "create temp file")
180 defer os.Remove(msgf.Name())
181 defer msgf.Close()
182 _, err = msgf.Write(content)
183 tcheck(t, err, "write message file")
184 err = acc.DeliverMailbox(ctl.log, "Inbox", m, msgf)
185 tcheck(t, err, "deliver message")
186 }
187
188 var msgBadSize store.Message
189 deliver(&msgBadSize)
190
191 msgBadSize.Size = 1
192 err = acc.DB.Update(ctxbg, &msgBadSize)
193 tcheck(t, err, "update message to bad size")
194 mb := store.Mailbox{ID: msgBadSize.MailboxID}
195 err = acc.DB.Get(ctxbg, &mb)
196 tcheck(t, err, "get db")
197 mb.Size -= int64(len(content))
198 mb.Size += 1
199 err = acc.DB.Update(ctxbg, &mb)
200 tcheck(t, err, "update mailbox size")
201
202 // Fix up the size.
203 ctlcmdFixmsgsize(ctl, "")
204
205 err = acc.DB.Get(ctxbg, &msgBadSize)
206 tcheck(t, err, "get message")
207 if msgBadSize.Size != int64(len(content)) {
208 t.Fatalf("after fixing, message size is %d, should be %d", msgBadSize.Size, len(content))
209 }
210 })
211
212 // "reparse"
213 testctl(func(ctl *ctl) {
214 ctlcmdReparse(ctl, "mjl")
215 })
216 testctl(func(ctl *ctl) {
217 ctlcmdReparse(ctl, "")
218 })
219
220 // "reassignthreads"
221 testctl(func(ctl *ctl) {
222 ctlcmdReassignthreads(ctl, "mjl")
223 })
224 testctl(func(ctl *ctl) {
225 ctlcmdReassignthreads(ctl, "")
226 })
227
228 // "backup", backup account.
229 err = dmarcdb.Init()
230 tcheck(t, err, "dmarcdb init")
231 err = mtastsdb.Init(false)
232 tcheck(t, err, "mtastsdb init")
233 err = tlsrptdb.Init()
234 tcheck(t, err, "tlsrptdb init")
235 testctl(func(ctl *ctl) {
236 os.RemoveAll("testdata/ctl/data/tmp/backup-data")
237 err := os.WriteFile("testdata/ctl/data/receivedid.key", make([]byte, 16), 0600)
238 tcheck(t, err, "writing receivedid.key")
239 ctlcmdBackup(ctl, filepath.FromSlash("testdata/ctl/data/tmp/backup-data"), false)
240 })
241
242 // Verify the backup.
243 xcmd := cmd{
244 flag: flag.NewFlagSet("", flag.ExitOnError),
245 flagArgs: []string{filepath.FromSlash("testdata/ctl/data/tmp/backup-data")},
246 }
247 cmdVerifydata(&xcmd)
248}
249