mlisting

Mailing list service

git clone git://git.lin.moe/go/mlisting.git

  1package storage
  2
  3import (
  4	"bytes"
  5	"context"
  6	"fmt"
  7	"io"
  8	"slices"
  9	"testing"
 10	"time"
 11
 12	"git.lin.moe/go/mlisting/tools/testdata"
 13	gomsg "github.com/emersion/go-message"
 14	"github.com/emersion/go-message/mail"
 15)
 16
 17func StorageTestListCRUD(t *testing.T, st Storage) {
 18	ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)
 19	defer cancel()
 20
 21	listname := func(i uint8) string {
 22		return fmt.Sprintf("TestList%d", i)
 23	}
 24	listaddr := func(i uint8) string {
 25		return fmt.Sprintf("testlist%d@base.lan", i)
 26	}
 27	listdesc := func(i uint8) string {
 28		return fmt.Sprintf("test description for list %d", i)
 29	}
 30	listperms := []uint8{
 31		PERM_BROWSE | PERM_REPLY | PERM_POST,
 32		PERM_BROWSE | PERM_REPLY,
 33		PERM_BROWSE,
 34	}
 35	var err error
 36
 37	init_lists, err := st.Lists(ctx)
 38	if err != nil {
 39		t.Fatal(err)
 40	}
 41	lists := make([]List, len(listperms))
 42
 43	for i, p := range listperms {
 44		lists[i], err = st.NewList(ctx, listname(p), listaddr(p), listdesc(p), p)
 45		if err != nil {
 46			t.Fatal(err)
 47		}
 48	}
 49	_, err = st.NewList(ctx, "", listaddr(listperms[0]), "", listperms[0])
 50	if err == nil {
 51		t.Fatal("lists in storage should not allow same address, but it has")
 52	}
 53
 54	_lists, err := st.Lists(ctx)
 55	if err != nil {
 56		t.Fatal(err)
 57	} else if len(_lists) != len(init_lists)+len(listperms) {
 58		t.Fatalf("got wrong list number, except %d, get %d", len(init_lists)+len(listperms), len(_lists))
 59	}
 60	for _, l := range lists {
 61		found_flag := false
 62		for _, _l := range _lists {
 63			if _l.Name() == l.Name() && _l.Address() == _l.Address() {
 64				found_flag = true
 65				break
 66			}
 67		}
 68		if found_flag != true {
 69			t.Fatal("not found added list in storage.Lists result")
 70		}
 71	}
 72
 73	for i, l := range lists {
 74		_l, err := st.GetList(ctx, l.Address())
 75		if err != nil {
 76			t.Fatal(err)
 77		}
 78		if _l.Name() != l.Name() || _l.Name() != listname(listperms[i]) {
 79			t.Fatal("get wrong list name from storage")
 80		}
 81		if _l.Address() != l.Address() || _l.Address() != listaddr(listperms[i]) {
 82			t.Fatal("get wrong list address from storage")
 83		}
 84		if _l.Description() != l.Description() || _l.Description() != listdesc(listperms[i]) {
 85			t.Fatal("get wrong list description from storage")
 86		}
 87		if _l.DefaultPerm() != l.DefaultPerm() || _l.DefaultPerm() != listperms[i] {
 88			t.Fatal("get wrong list default permission from storage")
 89		}
 90	}
 91
 92	for _, l := range lists {
 93		if err := st.DeleteList(ctx, l.Address()); err != nil {
 94			t.Fatal(err)
 95		}
 96	}
 97
 98	_lists, err = st.Lists(ctx)
 99	if err != nil {
100		t.Fatal(err)
101	} else if len(_lists) != len(init_lists) {
102		t.Fatal("clean lists failed")
103	}
104
105}
106
107func StorageTestMemberCRUD(t *testing.T, st Storage) {
108	const MEMBER_COUNT = 10
109	var (
110		str_members = make([]string, MEMBER_COUNT)
111		members     = make([]AddressInfo, MEMBER_COUNT)
112	)
113	for i := 0; i < MEMBER_COUNT; i += 1 {
114		str_members[i] = fmt.Sprintf("Member%d <mem%d@base.lan>", i, i)
115	}
116
117	ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)
118	defer cancel()
119
120	lists, err := st.Lists(ctx)
121	if err != nil {
122		t.Fatal(err)
123	} else if len(lists) == 0 {
124		t.Fatal("not found lists")
125	}
126
127	l1 := lists[0]
128	init_mems, err := l1.Members(ctx)
129	if err != nil {
130		t.Fatal(err)
131	}
132
133	for i, m := range str_members {
134		mem, err := l1.NewMember(ctx, m)
135		if err != nil {
136			t.Fatal(err)
137		}
138		members[i] = mem
139	}
140	_, err = l1.NewMember(ctx, members[0].Address())
141	if err == nil {
142		t.Fatal("members in same list should not have same address, but it has")
143	}
144
145	if mems, err := l1.Members(ctx); err != nil {
146		t.Fatal(err)
147	} else if len(mems) != len(init_mems)+MEMBER_COUNT {
148		t.Fatal("account wrong member number")
149	}
150
151	if l1.DefaultPerm() != members[0].Perm() {
152		t.Fatal("account wrong member default permmision")
153	}
154
155	for i, perm := range []uint8{
156		PERM_BROWSE | PERM_REPLY | PERM_POST,
157		PERM_BROWSE | PERM_REPLY,
158		PERM_BROWSE,
159	} {
160		mem, err := l1.UpdateMember(ctx, members[i].Address(), perm)
161		if err != nil {
162			t.Fatal(err)
163		}
164		members[i], err = l1.GetMember(ctx, members[i].Address())
165		if err != nil {
166			t.Fatal(err)
167		}
168		if mem.Perm() != perm || members[i].Perm() != perm {
169			t.Fatal("update list member permission failed")
170		}
171	}
172
173	for i, mem := range members {
174		if mem.Name() != fmt.Sprintf("Member%d", i) {
175			t.Fatalf("wrong member name, execpt %s, has %s", fmt.Sprintf("Member%d", i), mem.Name())
176		}
177		if mem.Address() != fmt.Sprintf("mem%d@base.lan", i) {
178			t.Fatalf("wrong member address, execpt %s, has %s", fmt.Sprintf("mem%d@base.lan", i), mem.Address())
179		}
180		joined_lists, err := mem.JoinedLists(ctx)
181		if err != nil {
182			t.Fatal(err)
183		}
184		var joined_flag = false
185		for _, jl := range joined_lists {
186			if jl.Address() == l1.Address() {
187				joined_flag = true
188				break
189			}
190		}
191		if joined_flag == false {
192			t.Fatal("not found joined list in member")
193		}
194	}
195
196	for _, mem := range members {
197		if err := l1.DelMember(ctx, mem.String()); err != nil {
198			t.Fatal(err)
199		}
200	}
201	if mems, err := l1.Members(ctx); err != nil {
202		t.Fatal(err)
203	} else if len(mems) != len(init_mems) {
204		t.Fatal("clean members failed")
205	}
206}
207
208func StorageTestMessageUpdate(t *testing.T, st Storage) {
209	ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)
210	defer cancel()
211
212	l, err := st.NewList(ctx, "test", "test@base.lan", "list for test", PERM_BROWSE|PERM_REPLY)
213	if err != nil {
214		t.Fatal(err)
215	}
216
217	// header backend data struct is dict, which is un-orderd
218	msgHeaderEqual := func(h1 gomsg.Header, h2 gomsg.Header) bool {
219		if h1.Len() != h2.Len() {
220			return false
221		}
222		f1 := h1.Fields()
223		for f1.Next() {
224			if f1.Value() != h2.Get(f1.Key()) {
225				return false
226			}
227		}
228		return true
229	}
230
231	// msgEqual has side effects: read entity.Body
232	msgEqual := func(msg1 *gomsg.Entity, msg2 *gomsg.Entity) bool {
233		if !msgHeaderEqual(msg1.Header, msg2.Header) {
234			return false
235		}
236		var (
237			buf1, buf2 *bytes.Buffer
238		)
239		buf1 = bytes.NewBuffer(nil)
240		io.Copy(buf1, msg1.Body)
241
242		buf2 = bytes.NewBuffer(nil)
243		io.Copy(buf2, msg2.Body)
244
245		return slices.Equal(buf1.Bytes(), buf2.Bytes())
246	}
247
248	for _, msg := range testdata.TestMessages {
249		header := gomsg.HeaderFromMap(msg.Header)
250
251		st_msg, err := l.AddMessage(ctx, msg.Header, msg.Body)
252
253		if err != nil {
254			t.Fatal(err)
255		}
256
257		if !msgHeaderEqual(header, st_msg.Header().Header) {
258			t.Fatal("messsage Headers from storage is not euqal to original headers")
259		}
260
261		ori_entity, _ := gomsg.New(gomsg.HeaderFromMap(msg.Header), bytes.NewReader(msg.Body))
262
263		if !msgEqual(ori_entity, st_msg.Entity()) {
264			t.Fatal("the raw message from storage is not euqal to the original message")
265		}
266	}
267
268	ori_msg := testdata.TestMessages[0]
269
270	_, err = l.AddMessage(ctx, ori_msg.Header, ori_msg.Body)
271	if err == nil {
272		t.Fatal("list should not add messages have same Message-ID header")
273	}
274
275	header := mail.HeaderFromMap(ori_msg.Header)
276	msgId, _ := header.MessageID()
277	msg, err := l.Message(ctx, msgId)
278	if err != nil {
279		t.Fatal(err)
280	}
281	ori_entity, _ := gomsg.New(gomsg.HeaderFromMap(ori_msg.Header), bytes.NewReader(ori_msg.Body))
282	if !msgEqual(ori_entity, msg.Entity()) {
283		t.Fatalf("query message by id get wrong result, query id %s, get %s",
284			ori_msg.Header.Get("Message-Id"), msg.Header().Get("Message-Id"))
285	}
286
287	if err := st.DeleteList(ctx, l.Address()); err != nil {
288		t.Fatal(err)
289	}
290}
291
292func StorageTestMessageQuery(t *testing.T, st Storage) {
293	ctx, cancel := context.WithTimeout(context.TODO(), time.Second*10)
294	defer cancel()
295
296	_l, err := st.Lists(ctx)
297	if err != nil {
298		t.Fatal(err)
299	} else if len(_l) == 0 {
300		t.Fatal("not found test lists")
301	}
302
303	l := _l[0]
304	msgs, _, err := l.Messages(ctx, true, "", 0, uint(len(testdata.TestMessages)))
305	if err != nil {
306		t.Fatal(err)
307	}
308	for _, msg := range msgs {
309		if msg.Header().Has("In-Reply-To") {
310			t.Log(msg.Header())
311			t.Fatal("get wrong message with In-Reply-To header")
312		}
313	}
314
315	msgs, _, err = l.Messages(ctx, false, "", 0, uint(len(testdata.TestMessages)))
316	if err != nil {
317		t.Fatal(err)
318	}
319	got_sub_msg := false
320	for _, msg := range msgs {
321		if msg.Header().Has("In-Reply-To") {
322			got_sub_msg = true
323		}
324	}
325	if !got_sub_msg {
326		t.Fatal("not found sub message")
327	}
328
329	header := mail.HeaderFromMap(testdata.TestMessages[0].Header)
330	msg_id, _ := (&header).MessageID()
331	key, _ := header.Subject()
332	msgs, total, err := l.Messages(ctx, false, key, 0, uint(len(testdata.TestMessages)))
333	is_searched := false
334	if total != 1 {
335		t.Fatalf("get multiple messages in the search result by subject %s", key)
336	}
337	for _, msg := range msgs {
338		if id, err := msg.Header().MessageID(); err != nil {
339			continue
340		} else if id == msg_id {
341			is_searched = true
342		}
343	}
344	if !is_searched {
345		t.Fatalf("not found message [%s] by keyword [%s]", msg_id, key)
346	}
347}